Question

Je suis réticent à dire que je ne peux pas comprendre cela, mais je ne peux pas comprendre cela. J'ai googlé et cherché Stack Overflow, et trouver vide.

Le résumé, et peut-être trop vague forme de la question est, Comment puis-je utiliser les traits-modèle aux fonctions membres instancier [Mise à jour: I utilisé le mauvais terme ici. Il devrait être « politiques » plutôt que « traits ». Classes existantes décrivent les caractéristiques. Les politiques prescrivent des catégories synthétiques.] La question a été soulevée tout en modernisant un ensemble de optimiseurs de fonction à plusieurs variables que j'ai écrit il y a plus de 10 ans.

Les optimiseurs fonctionnent tout en sélectionnant un chemin en ligne droite dans l'espace des paramètres loin le meilleur point courant (la « mise à jour »), puis de trouver un meilleur point sur cette ligne (la « recherche en ligne »), puis tester pour la condition "fait", et sinon fait, itérer.

Il existe différentes méthodes pour faire la mise à jour, la ligne de recherche, et peut-pour le test terminé, et d'autres choses. Mélanger et assortir. Différentes formules de mise à jour des données nécessitent des variables état. Par exemple, la mise à jour de LMQN nécessite un vecteur, et la mise à jour BFGS nécessite une matrice. Si l'évaluation des gradients est pas cher, la ligne de recherche devrait le faire. Dans le cas contraire, il doit utiliser des évaluations de la fonction uniquement. Certaines méthodes nécessitent plus précises ligne: recherche que d'autres. Ce sont que quelques exemples.

La version originale instancie plusieurs des combinaisons par des fonctions virtuelles. Certains traits sont sélectionnés en réglant les bits de mode qui sont testés lors de l'exécution. Beurk. Il serait trivial de définir les traits avec #define et les fonctions de membres avec #ifdef et de macros. Mais qu'il y a tellement de vingt ans. Il me tracasse que je ne peux pas trouver un moyen moderne whiz-bang.

S'il n'y avait qu'un seul trait qui variait, je pouvais utiliser le motif récurrent curieusement template. Mais je ne vois aucun moyen d'étendre ce à des combinaisons arbitraires de traits.

Je le fais en utilisant essayé boost::enable_if, etc .. Les informations d'état spécialisé était facile. J'ai réussi à obtenir les fonctions faites, mais seulement en recourant à des fonctions externes non-ami qui ont le this-pointeur comme paramètre. Je ne ai jamais compris comment faire les amis fonctions, les fonctions membres beaucoup moins. Le compilateur (VC ++ 2008) se plaignait toujours que les choses ne correspondent pas. Je criais, « SFINAE, crétin! » mais le crétin est probablement moi.

Peut-être étiquette-expédition est la clé. Je ne l'ai pas obtenu très profondément dans cela.

Certes, il est possible, non? Si oui, quelle est la meilleure pratique?

Mise à jour: Voici un autre essai à l'expliquer. Je veux que l'utilisateur soit en mesure de remplir un ordre (manifeste) pour un optimiseur personnalisé, quelque chose comme commander dans un menu chinois - une de la colonne A, une de la colonne B, etc .. Serveur, de la colonne A (updaters) , je vais avoir la mise à jour BFGS sauce Cholesky-une décomposition. A partir de la colonne B (ligne-chercheurs), je vais avoir la ligne de recherche d'interpolation cubique avec un eta de 0,4 et un rho de 1E-4, s'il vous plaît. Etc ...

Mise à jour: D'accord, d'accord. Voici le jeu autour que je l'ai fait. Je l'offre à contrecœur, parce que je pense qu'il est une approche complètement aberrant. Il fonctionne sous vc ++ 2008 bien.

#include <boost/utility.hpp>
#include <boost/type_traits/integral_constant.hpp>

namespace dj {

struct CBFGS {
    void bar() {printf("CBFGS::bar %d\n", data);}
    CBFGS(): data(1234){}
    int data;
};

template<class T>
struct is_CBFGS: boost::false_type{};

template<>
struct is_CBFGS<CBFGS>: boost::true_type{};

struct LMQN {LMQN(): data(54.321){}
    void bar() {printf("LMQN::bar %lf\n", data);}
    double data;
};

template<class T>
struct is_LMQN: boost::false_type{};

template<>
struct is_LMQN<LMQN> : boost::true_type{};

// "Order form"
struct default_optimizer_traits {
    typedef CBFGS update_type; // Selection from column A - updaters
};

template<class traits> class Optimizer;

template<class traits>
void foo(typename boost::enable_if<is_LMQN<typename traits::update_type>, 
         Optimizer<traits> >::type& self) 
{
    printf(" LMQN %lf\n", self.data);
}

template<class traits>
void foo(typename boost::enable_if<is_CBFGS<typename traits::update_type>,  
         Optimizer<traits> >::type& self) 
{
    printf("CBFGS %d\n", self.data);
}

template<class traits = default_optimizer_traits>
class Optimizer{
    friend typename traits::update_type;
    //friend void dj::foo<traits>(typename Optimizer<traits> & self); // How?
public:
    //void foo(void); // How???
    void foo() {
        dj::foo<traits>(*this);
    }
    void bar() {
        data.bar();
    }
//protected: // How?
    typedef typename traits::update_type update_type;
    update_type data;
};

} // namespace dj



int main() {
    dj::Optimizer<> opt;
    opt.foo();
    opt.bar();
    std::getchar();
    return 0;
}
Était-ce utile?

La solution

Je pense que la spécialisation de modèle est un pas dans la bonne direction. Cela ne fonctionne pas avec des fonctions si je suis passé à des classes. Je l'ai changé de sorte qu'il modifie les données. Je ne suis pas vendu sur les membres protégés et faire des amis. membres protégés sans héritage est une odeur. Le rendre public ou de fournir des accesseurs et le rendre privé.

template <typename>
struct foo;

template <>
struct foo<LMQN>
{
    template <typename OptimizerType>
    void func(OptimizerType& that)
    {
        printf(" LMQN %lf\n", that.data.data);
        that.data.data = 3.14;
    }
};

template <>
struct foo<CBFGS>
{
    template <typename OptimizerType>
    void func(OptimizerType& that)
    {
        printf(" CBFGS %lf\n", that.data.data);
    }
};

template<class traits = default_optimizer_traits>
class Optimizer{
public:
    typedef typename traits::update_type update_type;
    void foo() {
        dj::foo<typename traits::update_type>().func(*this);
    }
    void bar() {
        data.bar();
    }
    update_type data;
};

Autres conseils

Une solution simple pourrait être juste la transmission en fonction de l'étiquette d'utilisation, par exemple quelque chose comme ceci:

template<class traits>
void foo(Optimizer<traits>& self, const LMQN&) {
    printf(" LMQN %lf\n", self.data.data);
}

template<class traits>
void foo(Optimizer<traits>& self, const CBFGS&) {
    printf("CBFGS %d\n", self.data.data);
}

template<class traits = default_optimizer_traits>
class Optimizer {
    friend class traits::update_type;
    friend void dj::foo<traits>(Optimizer<traits>& self, 
                            const typename traits::update_type&);
public:
    void foo() {
        dj::foo<traits>(*this, typename traits::update_type());
    }
    void bar() {
        data.bar();
    }
protected:
    typedef typename traits::update_type update_type;
    update_type data;
};

Ou si vous souhaitez regrouper commodément plusieurs fonctions ensemble des caractères différents, peut-être quelque chose comme ceci:

template<class traits, class updater=typename traits::update_type> 
struct OptimizerImpl;

template<class traits>
struct OptimizerImpl<traits, LMQN> {
    static void foo(Optimizer<traits>& self) {
        printf(" LMQN %lf\n", self.data.data);
    }
};

template<class traits> 
struct OptimizerImpl<traits, CBFGS> {
    static void foo(Optimizer<traits>& self) {
        printf("CBFGS %d\n", self.data.data);
    }
};

template<class traits = default_optimizer_traits>
class Optimizer{
    friend class traits::update_type;
    friend struct OptimizerImpl<traits>;
public:
    void foo() {
        OptimizerImpl<traits>::foo(*this);
    }
    // ...
};

Il serait trivial de définir les traits avec #define et les fonctions de membres avec #ifdef et de macros. Mais qu'il y a tellement de vingt ans.

Bien qu'il puisse être utile de nouvelles méthodes d'apprentissage, les macros sont souvent le moyen le plus simple de faire des choses et ne doivent pas être mis au rebut comme un outil juste parce qu'ils sont « vieux ». Si vous regardez la MPL et à stimuler le livre sur TMP, vous trouverez beaucoup d'utilisation du préprocesseur.

Voici ce que je (OP) est venu avec. Pouvez-vous faire plus frais?

Les principales classes politiques de mise en œuvre de la classe modèle Optimizer. Il donne les classes accès aux membres protégés de l'optimiseur dont ils ont besoin. Une autre classe de modèle Optimizer divise le manifeste en ses parties constitutives et instancie le principal modèle Optimizer.

#include <iostream>
#include <cstdio>

using std::cout;
using std::endl;

namespace dj {

// An updater.
struct CBFGS {
    CBFGS(int &protect_)
        : protect(protect_)
    {}

    void update () {
        cout << "CBFGS " << protect << endl;
    }   

    // Peek at optimizer's protected data
    int &protect;

};

// Another updater
struct LMQN {
    LMQN(int &protect_)
        : protect(protect_)
    {}

    void update () {
        cout << "LMQN " << protect << endl;
    }

    // Peek at optimizer's protected data
    int &protect;

};

// A line-searcher
struct cubic_line_search {
    cubic_line_search (int &protect2_)
        : protect2(protect2_)
    {}

    void line_search() {
        cout << "cubic_line_search  " << protect2 << endl;
    }   

    // Peek at optimizer's protected data
    int &protect2;

};

struct default_search_policies {
    typedef CBFGS update_type;
    typedef cubic_line_search line_search_type;
};

template<class Update, class LineSearch>
class Opt_base: Update, LineSearch
{
public:
    Opt_base()
        : protect(987654321) 
        , protect2(123456789)
        , Update(protect)
        , LineSearch(protect2)
    {}
    void minimize() {
        update();
        line_search();
    }

protected:
    int protect;
    int protect2;
};

template<class Search_Policies=default_search_policies>
class Optimizer: 
    public Opt_base<typename Search_Policies::update_type
                  , typename Search_Policies::line_search_type
                    >
{};

} // namespace dj



int main() {
    dj::Optimizer<> opt; // Use default search policies
    opt.minimize();

    struct my_search_policies {
        typedef dj::LMQN update_type;
        typedef dj::cubic_line_search line_search_type;
    };

    dj::Optimizer<my_search_policies> opt2;
    opt2.minimize();

    std::getchar();
    return 0;
}

Votre utilisation de enable_if est un peu étrange. Je l'ai vu il utilisé seulement deux façons:

  • à la place du type de retour
  • en tant que paramètre supplémentaire (par défaut)

Utilisation pour un paramètre réel pourrait causer des ravages.

Quoi qu'il en soit, il est certainement possible de l'utiliser pour les fonctions membres:

template<class traits = default_optimizer_traits>
class Optimizer{
  typedef typename traits::update_type update_type;
public:

  typename boost::enable_if< is_LQMN<update_type> >::type
  foo()
  {
    // printf is unsafe, prefer the C++ way ;)
    std::cout << "LQMN: " << data << std::endl;
  }

  typename boost::enable_if< is_CBFGS<update_type> >::type
  foo()
  {
    std::cout << "CBFGS: " << data << std::endl;
  }


private:
  update_type data;
};

Notez que par défaut de rendement enable_if void, qui convient éminemment comme un type de retour dans la plupart des cas. La syntaxe « paramètre » est normalement réservé aux cas de constructeur, parce que vous ne disposez pas d'un type de retour à votre disposition alors, mais en général préfèrent utiliser le type de retour de sorte qu'il ne te mêle pas avec une résolution de surcharge.

EDIT :

La solution précédente ne fonctionne pas, comme il est indiqué dans les commentaires. Je ne pouvais trouver aucune alternative à l'aide enable_if, seule la voie de surcharge « de simple »:

namespace detail
{
  void foo_impl(const LMQN& data)
  {
    std::cout << "LMQN: " << data.data << std::endl;
  }

  void foo_impl(const CBFGS& data)
  {
    std::cout << "CBFGS: " << data.data << std::endl;
  }
} // namespace detail

template<class traits = default_optimizer_traits>
class Optimizer{
  typedef typename traits::update_type update_type;

public:
  void foo() { detail::foo_impl(data); }

private:
  update_type data;
};

Il est pas enable_if mais il fait le travail sans exposer de Optimizer à internals tout le monde. Kiss?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top