2010-05-12 7 views
19

Todo el material que he leído en Curiously Recurring Template Pattern parece una capa de herencia, es decir, Base y Derived : Base<Derived>. ¿Qué pasa si quiero dar un paso más?Cómo escribir plantillas curiosamente recurrentes con más de 2 capas de herencia?

#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; 
} 

¿Cómo puedo volver a escribir el código para compilar sin errores y pantalla

C
B

Usando c.get(). Print() y b.get ().impresión() ?

motivación: Supongamos que tengo tres clases,

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

Cada clase tiene 6 o más parámetros en el constructor, muchos de los cuales son opcionales y tienen valores predeterminados razonables. Para avoid the tedium of optional parameters, el mejor solution es usar el Named Parameter Idiom.

Un problema fundamental con este modismo es que las funciones de la clase de parámetro tienen que devolver el objeto al que están llamadas, aunque algunos parámetros se dan a GuiElement, algunos a Window y algunos a AlertBox. Se necesita una manera de escribir esto:

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

Sin embargo, esto probablemente fallará porque, por ejemplo, GuiElementParameter (int) devuelve probablemente GuiElementOptions &, que no tiene una función WindowParameter (int).

Esto ha sido asked antes, y la solución parece ser un poco de sabor del Patrón de Plantilla Curiosamente Recurrente. El sabor particular que uso es here.

Es mucho código para escribir cada vez que creo un nuevo Elemento Gui. He estado buscando formas de simplificarlo. Una causa principal de complejidad es el hecho de que estoy usando CRTP para resolver el problema Named-Parameter-Idiom, pero tengo tres capas, no dos (GuiElement, Window y AlertBox) y mi current workaround cuadruplica la cantidad de clases que tengo. (!) Por ejemplo, Window, WindowOptions, WindowBuilderT y WindowBuilder.

Eso me lleva a mi pregunta, en la que estoy esencialmente buscando una manera más elegante de usar CRTP en largas cadenas de herencia, como GuiElement, Window y Alertbox.

+0

¿Desea 'c.get(). Print()' dar salida a "C \ nB \ n" o desea que las líneas que comentó compilen y proporcionen el "B \ n "¿mitad? –

+0

Este último. Editado mi pregunta para ser más claro. – Kyle

+0

Debe indicar para qué quiere esto. Como es la única respuesta posible es: no, no puedes. –

Respuesta

2

Esto es lo que he decidido, usando una variación en CRTP para resolver el problema presentado en mi ejemplo de motivación. Probablemente es mejor leer a partir de la parte inferior y desplazándose hacia arriba ..

#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

No estoy del todo claro en lo que es la esperanza de lograr, pero esto es una buena aproximación de lo que parece estar pidiendo.

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; 
} 

Cambié la salida para ilustrar mejor la herencia. En su código original, no puede pretender que B no es una plantilla [lo mejor que puede esperar es B<>], por lo que algo como esto es probablemente la forma menos complicada de manejarlo.


De su otra respuesta, (2) no es posible. Puede dejar fuera los parámetros de la plantilla para las funciones, si los argumentos de la función son suficientes para inferirlos, pero con las clases debe proporcionar algo. (1) se puede hacer, pero es incómodo. Saliendo de todas las capas:

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 */ }; 

Tienes que hacer algo similar en cada nivel. Como dije, incómodo. No puede simplemente decir typename Derived = DefaultTag<B> > o algo similar porque B aún no existe.

+0

"No estoy del todo claro sobre lo que estás esperando lograr", agregó una larga sección de Motivación a mi pregunta. – Kyle

+0

Se me ocurrió una propuesta muy similar, pero también tuve una clase 'Cbase' (el mismo patrón que' Abase' y 'Bbase';' C' se deriva de él en lugar de 'Bbase'. La única ventaja, AFAIK, es que le permitiría seguir ampliando el patrón sin cambiar la herencia existente. –

0

Creo que es imposible implementar algún mecanismo genérico. Debe especificar explícitamente el parámetro de plantilla exacto cada vez que hereda la clase base, sin importar cuántos niveles de direccionamiento indirecto se coloquen entre (a juzgar por su respuesta: ahora hay 2 niveles: no pasa C directamente a la base, pero C envuelto en una estructura de etiqueta, parece una serpiente que muerde su propia cola)

Probablemente, sería mejor para su tarea usar borrado de tipo y no un patrón de plantilla curiosamente recurrente. Puede ser, this será útil

Cuestiones relacionadas