2012-05-15 25 views
6

Este problema es un poco difícil de explicar, por lo que se iniciará con un ejemplo:especialización de plantilla de subclases de la clase base de la plantilla

Tengo una plantilla de clase que tiene un tipo y una constante a medida que los parámetros de plantilla entero, y tengo un número de clases secundarias que se derivan de instancias de esa plantilla:

template <class V, int i> 
struct Base 
{ 
    static void doSomething() { cout << "something " << i << endl; }; 
}; 

struct Child : public Base<int,12> 
{ 
}; 

quiero usar estas clases con alguna otra plantilla (llamémoslo Test), que cuenta con especializaciones de diferentes tipos. Como el comportamiento debería ser exactamente el mismo para todas las clases que se derivan de cualquier instanciación de Base, quiero definir solo una especialización de Prueba que maneje todas las clases derivadas de Base.

Sé que no puedo especializarme directamente en Base < V, i > porque esto no detectaría las clases secundarias. En su lugar, mi primer acercamiento utilizaba enable_if y rasgos de tipo de Boost:

// empty body to trigger compiler error for unsupported types 
template <class T, class Enabled = void> 
struct Test { }; 

// specialization for ints, 
// in my actual code, I have many more specializations here 
template <class Enabled> 
struct Test <int, Enabled> 
{ 
    static void test (int dst) 
    { 
     cout << "Test<int>::test(" << dst << ")" << endl; 
    } 
}; 

// this should handle all subclasses of Base, 
// but it doesn't compile 
template <class T, class V, int i> 
struct Test <T, typename enable_if <is_base_and_derived <Base <V,i>, T>>::type> 
{ 
    static void test (const T &dst) 
    { 
     dst.doSomething(); 
    } 
}; 

int main (int argc, char **argv) 
{ 
    Test <int>::test (23); 
    Test <Child>::test (Child()); 
    return 0; 
} 

La idea era que la especialización debe manejar todas las clases que se derivan de base con todos los valores arbitrarios de V y yo. Esto no funciona, gcc se queja:

 
error: template parameters not used in partial specialization: 
error:   ‘V’ 
error:   ‘i’ 

Supongo que el problema es que este enfoque requeriría el compilador de probar todas las combinaciones posibles de V e I para comprobar si alguno de ellos se corresponda. Por ahora, he trabajado todo el problema añadiendo algo a la clase base:

template <class V, int i> 
struct Base 
{ 
    typedef V VV; 
    static constexpr int ii = i; 
    static void doSomething() { cout << "something " << i << endl; }; 
}; 

De esta manera, la especialización ya no necesita tener V e I parámetros de plantilla libres:

template <class T> 
struct Test <T, typename enable_if <is_base_and_derived <Base <typename T::VV, T::ii>, T>>::type> 
{ 
    static void test (const T &dst) 
    { 
     dst.doSomething(); 
    } 
}; 

Y luego compila

Ahora, mi pregunta es: ¿Cómo puedo hacer esto sin modificar la clase base? En este caso, fue posible porque lo escribí yo mismo, pero ¿qué puedo hacer si tengo que manejar código de biblioteca de terceros en mi plantilla de prueba de esa manera? ¿Hay una solución más elegante?

Editar: Además, ¿alguien me puede dar una explicación detallada de por qué exactamente el primer enfoque no funciona? Tengo una idea aproximada, pero preferiría tener una comprensión adecuada. :-)

Respuesta

3

una solución sencilla es dejar que Base hereda otra Base_base:

struct Base_base 
{}; 

template <class V, int i> 
struct Base 
: public Base_base 
{ 
    static void doSomething() { cout << "something " << i << endl; }; 
}; 

template <class T> 
struct Test <T, typename enable_if <is_base_and_derived <Base_base, T>>::type> 
{ 
    static void test (const T &dst) 
    { 
     dst.doSomething(); 
    } 
}; 

[Editado ] en un código de tercera parte, se puede utilizar un truco como:

template <class V, int i> 
struct Base3rdparty 
{ 
    static void doSomething() { cout << "something " << i << endl; }; 
}; 

template <class V, int i> 
struct Base 
: public Base3rdparty<V, i> 
{ 
    typedef V VV; 
    static constexpr int ii = i; 
}; 

template <class T> 
struct Test <T, typename enable_if <is_base_and_derived <Base <typename T::VV, T::ii>, T>>::type> 
{ 
    static void test (const T &dst) 
    { 
     dst.doSomething(); 
    } 
}; 
+0

Gracias por la sugerencia de Base_Base, eso hace que mi código actual sea un poco más legible. Sin embargo, para las bibliotecas de terceros, esto no funcionará, al menos no si las clases secundarias también pertenecen a la biblioteca. –

+1

@BenjaminSchug: quizás [este] (http://stackoverflow.com/a/6398983/1324131) responde a su nueva pregunta en una pregunta editada. – user2k5

+0

Gracias, esto explica por qué el primer enfoque no funcionó.Parece que mi instinto fue correcto. –

0

Utilice la función de sobrecarga y decltype:

// Never defined: 
template<typename T> std::false_type is_Base(T&); 
template<class V, int I> std::true_type is_Base(Base<V,I>&); 

template<typename IsBase, typename T> struct TestHelper; 
template<typename T> struct TestHelper<std::true_type, T> 
{ 
    static void test(const T& dst) { dst.doSomething(); } 
}; 
template<> struct TestHelper<std::false_type, int> 
{ 
    static void test(int dst) 
    { std::cout << "Test<int>::test(" << dst << ")" << std::endl; } 
}; 
// ... 

template<typename T> struct Test 
{ 
    static void test(const T& dst) 
    { TestHelper<decltype(is_Base(std::declval<T&>())), T>::test(dst); } 
} 
Cuestiones relacionadas