2010-05-12 6 views
19

Tout le matériel que j'ai lu sur Curlyly Recurring Template Pattern semble avoir une couche d'héritage, c'est-à-dire Base et Derived : Base<Derived>. Et si je veux aller plus loin?Comment écrire des modèles curieusement récurrents avec plus de 2 couches d'héritage?

#include <iostream> 
using std::cout; 


template<typename LowestDerivedClass> class A { 
public: 
    LowestDerivedClass& get() { return *static_cast<LowestDerivedClass*>(this); } 
    void print() { cout << "A\n"; } 
}; 
template<typename LowestDerivedClass> class B : public A<LowestDerivedClass> { 
    public: void print() { cout << "B\n"; } 
}; 
class C : public B<C> { 
    public: void print() { cout << "C\n"; } 
}; 

int main() 
{ 
    C c; 
    c.get().print(); 

// B b;    // Intentionally bad syntax, 
// b.get().print(); // to demonstrate what I'm trying to accomplish 

    return 0; 
} 

Comment puis-je réécrire ce code pour compiler sans erreurs et affichage

C
B

En utilisant c.get() impression.() Et b.get ().impression() ?

Motivation: Supposons que j'ai trois classes,

class GuiElement { /* ... */ }; 
class Window : public GuiElement { /* ... */ }; 
class AlertBox : public Window { /* ... */ }; 

Chaque classe prend 6 ou si les paramètres de leur constructeur, dont beaucoup sont optionnelles et ont des valeurs par défaut raisonnables. Pour avoid the tedium of optional parameters, le meilleur solution est d'utiliser le Named Parameter Idiom. Un problème fondamental avec cet idiome est que les fonctions de la classe de paramètre doivent renvoyer l'objet sur lequel elles sont appelées, cependant certains paramètres sont donnés à GuiElement, certains à Window, et d'autres à AlertBox. Vous avez besoin d'une façon d'écrire ceci:

AlertBox box = AlertBoxOptions() 
    .GuiElementParameter(1) 
    .WindowParameter(2) 
    .AlertBoxParameter(3) 
    .create(); 

Pourtant, ce échouerait probablement parce que, par exemple, GuiElementParameter (int) retourne probablement GuiElementOptions &, qui ne dispose pas d'une fonction WindowParameter (int).

Cela a été précédemment asked, et la solution semble être une certaine saveur du modèle de modèle curieusement récurrent. La saveur particulière que j'utilise est here.

C'est beaucoup de code à écrire chaque fois que je crée un nouvel élément Gui. J'ai cherché des moyens de le simplifier. Une des principales causes de complexité est le fait que j'utilise CRTP pour résoudre le problème Named-Parameter-Idiom, mais j'ai trois couches et non deux (GuiElement, Window et AlertBox) et mon current workaround quadruple le nombre de classes que j'ai. (!) Par exemple, Window, WindowOptions, WindowBuilderT et WindowBuilder. Cela m'amène à ma question, où je suis essentiellement à la recherche d'une façon plus élégante d'utiliser CRTP sur de longues chaînes d'héritage, telles que GuiElement, Window, et AlertBox.

+0

Voulez-vous 'c.get(). Print()' pour sortir "C \ nB \ n" ou voulez-vous que les lignes que vous avez commentées pour compiler et fournir le "B \ n "moitié? –

+0

Ce dernier. Edité ma question pour être plus clair, je l'espère. – Kyle

+0

Vous devriez indiquer ce que vous voulez pour cela. Comme c'est la seule réponse possible est: non, vous ne pouvez pas. –

Répondre

2

Voici ce que j'ai décidé, en utilisant une variation sur CRTP pour résoudre le problème présenté dans mon exemple de motivation. Probablement mieux lu à partir du bas et défilement vers le haut ..

#include "boost/smart_ptr.hpp" 
using namespace boost; 

// *** First, the groundwork.... 
//  throw this code in a deep, dark place and never look at it again 
// 
//  (scroll down for usage example) 

#define DefineBuilder(TYPE, BASE_TYPE) \ 
    template<typename TargetType, typename ReturnType> \ 
    class TemplatedBuilder<TYPE, TargetType, ReturnType> : public TemplatedBuilder<BASE_TYPE, TargetType, ReturnType> \ 
    { \ 
    protected: \ 
     TemplatedBuilder() {} \ 
    public: \ 
     Returns<ReturnType>::me; \ 
     Builds<TargetType>::options; \ 

template<typename TargetType> 
class Builds 
{ 
public: 
    shared_ptr<TargetType> create() { 
     shared_ptr<TargetType> target(new TargetType(options)); 
     return target; 
    } 

protected: 
    Builds() {} 
    typename TargetType::Options options; 
}; 

template<typename ReturnType> 
class Returns 
{ 
protected: 
    Returns() {} 
    ReturnType& me() { return *static_cast<ReturnType*>(this); } 
}; 

template<typename Tag, typename TargetType, typename ReturnType> class TemplatedBuilder; 
template<typename TargetType> class Builder : public TemplatedBuilder<TargetType, TargetType, Builder<TargetType> > {}; 

struct InheritsNothing {}; 
template<typename TargetType, typename ReturnType> 
class TemplatedBuilder<InheritsNothing, TargetType, ReturnType> : public Builds<TargetType>, public Returns<ReturnType> 
{ 
protected: 
    TemplatedBuilder() {} 
}; 

// *** preparation for multiple layer CRTP example *** // 
//  (keep scrolling...) 

class A    
{ 
public: 
    struct Options { int a1; char a2; }; 

protected: 
    A(Options& o) : a1(o.a1), a2(o.a2) {} 
    friend class Builds<A>; 

    int a1; char a2; 
}; 

class B : public A 
{ 
public: 
    struct Options : public A::Options { int b1; char b2; }; 

protected: 
    B(Options& o) : A(o), b1(o.b1), b2(o.b2) {} 
    friend class Builds<B>; 

    int b1; char b2; 
}; 

class C : public B 
{ 

public: 
    struct Options : public B::Options { int c1; char c2; }; 

private: 
    C(Options& o) : B(o), c1(o.c1), c2(o.c2) {} 
    friend class Builds<C>; 

    int c1; char c2; 
}; 


// *** many layer CRTP example *** // 

DefineBuilder(A, InheritsNothing) 
    ReturnType& a1(int i) { options.a1 = i; return me(); } 
    ReturnType& a2(char c) { options.a2 = c; return me(); } 
}; 

DefineBuilder(B, A) 
    ReturnType& b1(int i) { options.b1 = i; return me(); } 
    ReturnType& b2(char c) { options.b2 = c; return me(); } 
}; 

DefineBuilder(C, B) 
    ReturnType& c1(int i) { options.c1 = i; return me(); } 
    ReturnType& c2(char c) { options.c2 = c; return me(); } 
}; 

// note that I could go on forever like this, 
// i.e. with DefineBuilder(D, C), and so on. 
// 
// ReturnType will always be the first parameter passed to DefineBuilder. 
// ie, in 'DefineBuilder(C, B)', ReturnType will be C. 

// *** and finally, using many layer CRTP builders to construct objects ***/ 

int main() 
{ 
    shared_ptr<A> a = Builder<A>().a1(1).a2('x').create(); 
    shared_ptr<B> b = Builder<B>().a1(1).b1(2).a2('x').b2('y').create(); 
    shared_ptr<B> c = Builder<C>().c2('z').a1(1).b1(2).a2('x').c1(3).b2('y').create(); 
    // (note: any order works) 

    return 0; 
}; 
7

Je ne suis pas tout à fait clair sur ce que vous espérez accomplir, mais c'est une bonne approximation de ce que vous semblez demander.

template<typename LowestDerivedClass> class A { 
public: 
    LowestDerivedClass& get() { return *static_cast<LowestDerivedClass*>(this); } 
    void print() { cout << "A"; } 
}; 
template<typename LowestDerivedClass> class Bbase 
    : public A<LowestDerivedClass> { 
public: void print() { cout << "B"; this->A<LowestDerivedClass>::print(); } 
}; 

class B : public Bbase<B> { 
}; 

class C : public Bbase<C> { 
public: void print() { cout << "C"; this->Bbase<C>::print(); } 
}; 

int main() { 
    C c; 
    c.print(); 
    cout << endl; 
    B b; 
    b.print(); 
    cout << endl; 
} 

J'ai modifié la sortie pour mieux illustrer l'héritage. Dans votre code d'origine, vous ne pouvez pas prétendre que B n'est pas un modèle [le meilleur que vous pouvez espérer est B<>], donc quelque chose comme ceci est probablement la façon la moins kludgy de le gérer.


De votre autre réponse, (2) n'est pas possible. Vous pouvez ignorer les paramètres de modèle pour les fonctions, si les arguments de la fonction sont suffisants pour les déduire, mais avec les classes, vous devez fournir quelque chose. (1) peut être fait, mais c'est maladroit. Laissant de côté toutes les différentes couches:

template<typename T> struct DefaultTag { typedef T type; }; 
template<typename Derived = void> 
class B : public A<Derived> { /* what B should do when inherited from */ }; 
template<> 
class B<void> : public A<DefaultTag<B<void> > > { /* what B should do otherwise */ }; 

Vous devez faire quelque chose de similaire à chaque niveau. Comme je l'ai dit, maladroit. Vous ne pouvez pas simplement dire typename Derived = DefaultTag<B> > ou quelque chose de similaire parce que B n'existe pas encore.

+0

"Je ne suis pas tout à fait clair sur ce que vous espérez accomplir" - a ajouté une longue section de motivation à ma question. – Kyle

+0

Je suis arrivé avec une proposition très similaire, mais j'avais aussi une classe 'Cbase' (même modèle que' Abase' et 'Bbase';' C' en dérive au lieu de 'Bbase'.) Le seul avantage, AFAIK, est Cela vous permettrait de continuer à étendre le modèle sans modifier l'héritage existant. –

0

Je pense qu'il est impossible d'implémenter un mécanisme générique. Vous devez spécifier explicitement le paramètre modèle exact chaque fois que vous héritez de la classe de base, quel que soit le nombre de niveaux d'indirection (à en juger par votre réponse: maintenant il y a 2 niveaux: vous ne passez pas C directement à la base. C enveloppé dans une structure de tag, il ressemble à un serpent qui mord sa propre queue)

Probablement, il serait préférable pour votre tâche d'utiliser l'effacement de type, et non curieusement modèle de modèle récurrent. Peut être, this sera utile