2010-06-09 12 views
15

Estoy intentando escribir código que usa un miembro typedef de un argumento de plantilla, pero quiero proporcionar un tipo predeterminado si el argumento de plantilla no tiene ese typedef. Un ejemplo simplificado que he probado es la siguiente:Especialización de plantillas para usar el tipo predeterminado si el miembro de clase typedef no existe

struct DefaultType { DefaultType() { printf("Default "); } }; 
struct NonDefaultType { NonDefaultType() { printf("NonDefault "); } }; 

struct A {}; 
struct B { typedef NonDefaultType Type; }; 

template<typename T, typename Enable = void> struct Get_Type { 
    typedef DefaultType Type; 
}; 
template<typename T> struct Get_Type< T, typename T::Type > { 
    typedef typename T::Type Type; 
}; 

int main() 
{ 
    Get_Type<A>::Type test1; 
    Get_Type<B>::Type test2; 
} 

yo esperaría esta opción para imprimir "no predeterminado por defecto", pero en su lugar se imprime "por defecto por defecto". Mi expectativa es que la segunda línea en main() debe coincidir con la versión especializada de Get_Type, porque B :: Type existe. Sin embargo, esto no sucede.

¿Alguien puede explicar lo que está pasando aquí y cómo solucionarlo, u otra forma de lograr el mismo objetivo?

Gracias.

Editar:

Georg dio un método alternativo, pero todavía estoy curioso acerca de por qué esto no funciona. Según el impulso del enable_if docs, una manera de especializarse una plantilla para diferentes tipos es de esta manera:

template <class T, class Enable = void> 
class A { ... }; 

template <class T> 
class A<T, typename enable_if<is_integral<T> >::type> { ... }; 

template <class T> 
class A<T, typename enable_if<is_float<T> >::type> { ... }; 

Esto funciona porque enable_if < cierto> tiene el tipo como un typedef, pero enable_if < falsa> no lo hace.

No entiendo cómo es esto diferente de mi versión, donde en lugar de usar enable_if solo estoy usando T :: Type directamente. Si existe T :: Type, ¿no sería lo mismo que enable_if < true> :: escribir en el ejemplo anterior y hacer que se elija la especialización? Y si T :: Type no existe, ¿no sería lo mismo que enable_if < false> :: type no existir y hacer que la versión predeterminada sea elegida en el ejemplo anterior?

+0

Uh .. ¿cuál es el objetivo? –

+0

El objetivo es que Get_Type :: Type sea T :: Type si existe, o DefaultType si no existe. – Frank

Respuesta

8

Para responder a su adición, su argumento de especialización pasa al miembro typedef y espera que produzca void como tipo. No tiene nada de mágico, solo usa un argumento predeterminado. Vamos a ver cómo funciona. Si dice Get_Type<Foo>::type, el compilador usa el argumento predeterminado Enable, que es void, y el nombre del tipo se convierte en Get_Type<Foo, void>::type. Ahora, el compilador verifica si alguna especialización parcial coincide.

La lista de argumentos de su especialización parcial <T, typename T::Type> se deduce de la lista de argumentos original <Foo, void>. Esto deducirá T a Foo y luego sustituye ese Foo en el segundo argumento de la especialización, obteniendo un resultado final de <Foo, NonDefaultType> para su especialización parcial. Sin embargo, eso no coincide con la lista de argumentos original <Foo, void> en absoluto.

Usted necesita una manera de producir el tipo void, como en el siguiente:

template<typename T> 
struct tovoid { typedef void type; }; 

template<typename T, typename Enable = void> struct Get_Type { 
    typedef DefaultType Type; 
}; 
template<typename T> 
struct Get_Type< T, typename tovoid<typename T::Type>::type > { 
    typedef typename T::Type Type; 
}; 

Ahora bien, esto va a funcionar como se espera. El uso de MPL, se puede utilizar en lugar de alwaystovoid

typename apply< always<void>, typename T::type >::type 
+0

Gracias Johannes. No me di cuenta de que el tipo en sí tenía que ser vacío para que coincida, pero eso tiene perfecto sentido. – Frank

7

Usted puede hacer que mediante la utilización SFINAE:

template<class T> struct has_type { 
    template<class U> static char (&test(typename U::Type const*))[1]; 
    template<class U> static char (&test(...))[2]; 
    static const bool value = (sizeof(test<T>(0)) == 1); 
}; 

template<class T, bool has = has_type<T>::value> struct Get_Type { 
    typedef DefaultType Type; 
}; 

template<class T> struct Get_Type<T, true> { 
    typedef typename T::Type Type; 
}; 
+0

Gracias George, eso funciona. Edité mi pregunta con otra pregunta sobre por qué mi implementación no funciona, porque parece ser el mismo principio en el que se basa enable_if. enable_if tiene un typedef, pero enable_if no. No entiendo por qué eso no es lo mismo que si T :: Type existe o no. – Frank

+0

@Frank: Johannes ya respondió muy bien en otra respuesta. –

4

Primer paso: dejar de usar "Tipo" y utilizar el estándar de mpl "tipo".


BOOST_MPL_HAS_XXX_DEF(Type) 

template < typename T > 
struct get_type { typedef typename T::Type type; }; 

template < typename T > 
struct calculate_type : boost::mpl::if_ 
< 
    has_Type<T> 
, get_type<T> 
, boost::mpl::identity<default_type> 
>::type {} 

typedef calculate_type<A>::type whatever; 

Si ha utilizado "tipo" en lugar de "Tipo" en sus metafunciones que no requeriría la función de obtención "get_type" para convertir y sólo podría volver T en ese caso.

Cuestiones relacionadas