2011-09-05 8 views
6

Estoy usando mi clase como un parámetro de plantilla de una de sus clases principales, y esa clase principal lo usa en un argumento de plantilla (aunque sizeof()).Usar clase infantil como un parámetro de plantilla de una clase base y como un especificador de nombre anidado

y el compilador me da:

error: tipo incompleto 'invocador :: workerClass {alias} MyClass' utilizarse en el nombre anidado especificador

Sin embargo, la clase está bien definido en el archivo . Supongo que esto se debe a que la clase hija no está instanciada en el momento de la instanciación de la clase base, pero ese tipo de cosas ocurre con CRTP y no hay problemas.

La razón por la que uso la clase secundaria en el argumento de plantilla es hacer una llamada a función diferente si la clase secundaria tiene, o no tiene, una función específica.

Aquí hay un código mínimo para las pruebas

/* Structure similar to boost's enable if, to use 
    SFINAE */ 
template <int X=0, class U = void> 
struct test { 
    typedef U type; 
}; 

enum Commands { 
    Swim, 
    Fly 
}; 

/* Structure used for template overloading, 
    as no partial function template specialization available */ 
template<Commands T> 
struct Param { 

}; 

template <class T> 
class Invoker 
{ 
public: 
    typedef T workerClass; 

    workerClass *wc() { 
     return static_cast<workerClass*>(this); 
    } 

    template <Commands command> 
    void invoke() { 
     invoke2(Param<command>()); 
    } 

    /* If the child class has those functions, call them */ 
    /* Needs template paramter Y to apply SFINAE */ 
    template<class Y=int> 
    typename test<sizeof(Y)+sizeof(decltype(&workerClass::fly))>::type 
    invoke2(Param<Fly>) { 
     wc()->fly(); 
    } 

    template<class Y=int> 
    typename test<sizeof(Y)+sizeof(decltype(&workerClass::swim))>::type 
    invoke2(Param<Swim>) { 
     wc()->shoot(); 
    } 

    template<Commands command> 
    void invoke2(Param<command>) { 
     /* Default action */ 
     printf("Default handler for command %d\n", command); 
    } 
}; 

template <class T, class Inv = Invoker<T> > 
class BaseClass : public Inv 
{ 
public: 
    template<Commands command> 
    void invoke() { 
     Inv::template invoke<command>(); 
    } 
}; 

class MyClass : public BaseClass<MyClass> 
{ 
public: 
    void swim() { 
     printf("Swimming like a fish!\n"); 
    } 

    /* void fly(); */ 
}; 


void testing() { 
    MyClass foo; 
    foo.invoke<Fly>(); /* No 'void fly()' in MyClass, calls the default handler */ 
    foo.invoke<Swim>(); /* Should print the swimming message */ 
} 

El error ocurre en la línea de:

typename test<sizeof(Y)+sizeof(decltype(&workerClass::fly))>::type 

Así que, ¿hay algún compilador que apoya esta, o se trata explícitamente especificado por la norma como un uso no válido de las plantillas? ¿Tengo que cambiar la forma en que estoy haciendo esto y encontrar un camino? CRTP me da esperanzas de que el código sea válido, pero no estoy seguro.

Si esto realmente no es posible, entonces ¿por qué exactamente y por qué funciona el CRTP?

+4

Pues bien, el problema es claro, al menos: Usted está haciendo una definición recursiva autorreferencial: '' BaseClass requiere la completa 'MyClass' debido a la' decltype 'expresión, pero' MyClass' requiere una 'BaseClass ' completa como clase base. –

+1

Necesita ocultar las especializaciones detrás de una capa adicional de direccionamiento indirecto para que no se evalúen como parte de la definición de la clase base. Póngalos en un tipo anidado privado y todo funcionará bien. – ildjarn

+0

CRTP es complicado precisamente por esta razón, el problema al que se enfrenta es simple de entender: la plantilla base se instancia como parte de la declaración de clase, en un punto donde aún no está completa (piense que el tipo todavía depende de lo que la plantilla base puede agregar, por lo que no hay forma de que el compilador pueda saber cómo será el tipo derivado sin procesar primero la plantilla base --considere que la base podría agregar atributos de miembro que cambian el tamaño, o funciones de miembros virtuales que podrían cambiar el significado de las declaraciones de funciones en el tipo derivado) –

Respuesta

2

La solución fue, como ildjarn señaló, para agregar otro nivel de indirección.

Eso se hace cambiando la función de prueba para aceptar tipos:

template <typename X, class U = void> 
struct test { 
    typedef U type; 
}; 

Y luego pasar la clase hijo como un parámetro de plantilla, en lugar de especificar que desde el ir:

template<class Y=workerClass> 
    typename test<decltype(&Y::fly)>::type 
    invoke2(Param<Fly>) { 
     wc()->fly(); 
    } 

    template<class Y=workerClass> 
    typename test<decltype(&Y::swim)>::type 
    invoke2(Param<Swim>) { 
     wc()->swim(); 
    } 

De esta forma, el especificador anidado se evalúa solo cuando se llama a la función y no en la evaluación de la clase, y para ese momento la clase hija ya está evaluada. Además, con la posibilidad de pasar el argumento de plantilla predeterminado, podemos llamar a la función sin ningún parámetro de plantilla.

La plantilla también es mucho más legible ahora. Y el código de ejemplo funciona bien ahora:

class MyClass : public BaseClass<MyClass> 
{ 
public: 
    void swim() { 
     printf("Swimming like a fish!\n"); 
    } 

    /* void fly(); */ 
}; 


void testing() { 
    MyClass foo; 
    foo.invoke<Fly>(); /* No 'void fly()' in MyClass, calls the default handler */ 
    foo.invoke<Swim>(); /* Should print the swimming message */ 
} 
+0

Esto es muy interesante. ¿Alguien puede proporcionar un caso de uso interesante para esto? – Zhro

+0

@Zhro de la manera en que lo usé es para hacer un sistema como funciones virtuales sin la sobrecarga (insignificante) inducida por la tabla virtual. – coyotte508

Cuestiones relacionadas