2010-06-27 4 views
2

S'il vous plaît considérer cette -probablement mal written- exemple:Quelle est la meilleure façon d'avoir un nombre variable de paramètres de modèle?

class Command; 

class Command : public boost::enable_shared_from_this<Command> 
{ 
    public : 
    void execute() 
    { 
    executeImpl(); 
       // then do some stuff which is common to all commands ... 
    } 

    // Much more stuff ... 
    private: 
     virtual void executeImpl()=0; 
     // Much more stuff too ... 
}; 

et:

class CmdAdd : public Command 
{ 
public: 
    CmdAdd(int howMuchToAdd); 
    void executeImpl(); 


    int _amountToAdd; 
}; 

// implementation isn't really important here .... 

Avec cela, je peux simplement ajouter un rappel à l'aide de cette syntaxe:

 boost::shared_ptr<Command> cmdAdd(CmdAdd(someValue)); 
    cmdAdd->execute(); 

Il fonctionne parfaitement. Ma classe "Command" fait beaucoup plus de choses qui sont communes à toutes les commandes, telles que l'annulation, le rétablissement, le rapport de progression, etc., mais je l'ai enlevé du code pour des raisons de lisibilité.

Ma question est simple: est-il un moyen de réécrire la classe de commande, afin que je puisse remplacer cet appel:

boost::shared_ptr<Command> cmdAdd(CmdAdd(someValue)); 
cmdAdd->execute(); 

par quelque chose comme:

CmdAdd(someValue); // preferably 
or CmdAdd->execute(someValue) 

J'ai pensé beaucoup, mais j'ai un problème conceptuel: Je voulais template ma classe de commande comme

template <typename R,typename T1, typename T2, ..., typename Tn> class Command 
{ 
    R1 execute(T1 p1, ...,Tn pn) 
    { 
     return executeImpl(T1 p1, ...,Tn pn); 
     // then do some stuff which is common to all commands ... 
    } 
} 

mais évidemment, il y a un problème ici: la syntaxe template <typename R,typename T1, typename T2, ..., typename Tn> n'est pas légale C++, AFAIK.

Dois-je écrire n versions de commande, comme:

template <typename R> class Command 
template <typename R,typename T1> class Command 
template <typename R,typename T1, typename T2> class Command 
... 

et ainsi de suite? (même pas sûr que ça va fonctionner en effet)

Ou y at-il une autre façon plus élégante de le faire? La syntaxe, mentionnée here est-elle utilisée? (fonction f;)

J'ai regardé les listes de types de Loki et ils semblent faire le travail. Mais je ne trouve rien dans Boost. Je lis sur le web que boost :: mpl est ce que l'on veut utiliser pour mettre en œuvre des typelists, mais je suis un peu confus par docs MPL?

Des idées à ce sujet? Regads, D.

Répondre

2

Question intéressante :)

Tout d'abord, il y a un problème que vous négligé: vous avez besoin d'une classe de base commune pour tous Command et cette classe ne peut pas être basé sur un modèle si vous allez utiliser une pile d'entre eux (pour défaire refaire).

donc vous êtes coincé avec:

class Command 
{ 
public: 
    void execute(); 
private: 
    virtual void executeImpl() = 0; 
}; 

Je comprends votre désir d'exécuter la fonction avec des paramètres, mais ne pas oublier que vous quand même besoin d'enregistrer les paramètres de l'opération undo/redo. Il est simplement plus simple de les faire passer par le constructeur.

Cependant, vous pouvez toujours utiliser une méthode pour appeler réellement basé sur un modèle d'une commande:

template <class Command> 
void execute() { Command cmd; cmd.execute(); } 

template <class Command, class T0> 
void execute(T0& arg0) { Command cmd(arg0); cmd.execute(); } 

/// ... 

int main(int argc, char* argv[]) 
{ 
    execute<MyLittleCommand>("path", 3); 
} 

qui est proche de la syntaxe que vous désirez. Notez que j'ai délibérément oublié la pile ici, dans mon esprit, vous devez passer à la méthode execute pour l'enregistrement (une fois terminé).

pas que je aussi probablement changer la conception Command pour se rapprocher d'un modèle de stratégie:

struct CommandImpl 
{ 
    virtual ~CommandImpl(); 
    virtual void executeImpl() = 0; 
}; 

class Command 
{ 
public: 
    template <class C> 
    static Command Make() { return Command(new C()); } 

    template <class C, class T0> 
    static Command Make(T0& arg0) { return Command(new C(arg0)); } 

    /// .... 

    void execute(CommandStack& stack) 
    { 
    mImpl->executeImpl(); 
    stack.Push(*this); 
    } 

private: 
    Command(CommandImpl* c): mImpl(c) {} 
    boost::shared_ptr<CommandImpl> mImpl; 
}; 

Il est la combinaison typique de l'interface non virtuel et le pointeur sur les idiomes de mise en œuvre.

+0

Très intéressé! En effet, j'ai ajouté une classe CommandBase non-template, précisément pour les conteneurs. Enregistrer/restaurer les paramètres fonctionne également parfaitement (quelques astuces). J'ai finalement choosed d'utiliser cette approche "modèle classe Command template Classe de commande template classe Command ", avec boost préprocesseur. Semble être le plus astuce, mais j'ai encore des problèmes de syntaxe. Je trouve que votre approche est beaucoup plus élégante que la mienne, alors merci beaucoup et je vais jeter un coup d'œil à ce modèle :-) – Dinaiz

2

AFAIK Vous ne pouvez pas vraiment faire cela avec la norme actuelle de C++. Certains codes boost utilisent des macros et d'autres pré-traitements pour simuler des templates variés (je pense que boost :: pool ou boost :: object_pool utilisent quelque chose comme ça).

Cependant, variadic templates arrivent dans la prochaine norme C++ 0x et selon cette page GCC déjà fournir une implémentation à partir de v4.3: http://gcc.gnu.org/projects/cxx0x.html

Si vous utilisez, vous pouvez l'activer en l'activation de C++ 0x.

+0

+1 Ceci est la vraie réponse. Cependant étant donné que ce nouveau C++ n'est pas encore supporté par tous les compilateurs: A quel point voulez-vous que votre code soit portable? Est-ce qu'il doit être compilable sous la plupart des compilateurs ou est-ce qu'un seul compilateur spécifique suffit pour vous? –

+1

Les modèles variadiques ne résolvent pas le problème, voir ma réponse. – fredoverflow

0

Les modèles variés, comme l'a souligné Klaim, sont la solution ultime à ce problème.Cependant, il existe un moyen de permettre à un nombre variable d'arguments de modèle à l'aide des listes de type:

template <class H, class T> 
struct typelist 
{ 
    typedef H head; 
    typedef T tail; 
}; 

Cela vous permet d'écrire typelist<typelist<int, float>, double>, par exemple. Cependant, c'est une vraie douleur dans le cou de lire et d'écrire, et c'est la principale raison pour laquelle boost :: function utilise l'approche brute force (classe séparée pour chaque nombre d'arguments template): boost :: function0, boost :: function1 , boost :: function2, etc. pour son implémentation backend. C'est tellement plus facile que de parcourir les typologies récursivement à travers la métaprogrammation de template. Pour une réponse générale, je l'ai posté dans l'autre fil où vous aviez cette question à l'origine avec un autre.

+0

Je me demandais si en utilisant la spécialisation de modèle partielle, il est possible d'utiliser le système de boost, mais avec un seul nom de fonction, je.e, au lieu de: function0 <> function1 ont fonction <> fonction et le compilateur choisit la bonne version en fonction du nombre d'arguments de modèle que vous fournissez: MyFunction: Public Function -> would "choose" function1 Si je comprends ce que vous dites, il faut écrire MyFunction: public Function1 pour que cela fonctionne, non? – Dinaiz

2

À première vue, les modèles variés semblent être la solution parfaite. Malheureusement, ils ne jouent pas bien avec des fonctions virtuelles:

template <typename... Args> 
void execute(Args&&... args) 
{ 
    executeImpl(std::forward<Args>(args)...); 
} 

Cela nécessite executeImpl être une fonction de membre virtuel modèle, mais il n'y a pas une telle chose en C++!

+0

+1 pour la bonne prise. Mais la façon dont il montre fonctionnera toujours. Comme 'template class Commande; template struct Commande {/ * ... */virtual R executeImpl (P ...) = 0; }; 'Puis il peut l'implémenter par la classe CmdAdd: commande publique {void executeImpl (int commentMais) {/ * ... * /}};' –

+0

@Johannes: Votre solution implique que toutes les sous-classes utilisent la même signature de fonction , droite? Est-ce ce que veut Dinaiz? Je ne suis pas sûr. – fredoverflow

+1

Je suis d'accord, c'est juste qu'il n'y a pas de classe de base commune à toutes les commandes :) –

Questions connexes