2011-02-27 14 views
6

Normalmente declaro mis clases y plantillas, y luego defino sus métodos después (en el mismo archivo de encabezado, por supuesto). Simplemente me resulta más fácil leer de esa manera. Bueno, me he encontrado con un caso en el que no puedo encontrar una firma de tipo de trabajo para usar en una definición fuera de clase. Aquí está un ejemplo simplificado de lo que estoy haciendo, que ilustra el problema:¿firma de tipo de un constructor de plantilla con plantilla habilitada?

template <class T> 
struct Foo 
    { 
    Foo(T a, T b); 

    template 
     < class Iterator 
     , enable_if< is_iterator<Iterator> > 
     > 
    Foo 
     (Iterator first 
     , Iterator last 
    ); 
    }; 

template <class T> 
Foo<T>::Foo(T a, T b) 
{ ... } 

template <class T> 
template 
    < class U 
    , WHAT_GOES_HERE? 
    > 
Foo<T>::Foo(U f, U l) 
{ ... } 

He intentado varias cosas en la ranura WHAT_GOES_HERE para tratar de obtener una firma coincidente, y me siguen fallando. Necesito el enable_if para distinguir el caso donde uno pasa en dos objetos de tipo T, y cuando uno pasa en un par de iteradores. El código funciona bien si el constructor con plantilla se define dentro de la plantilla principal, que es como lo hace actualmente el código, pero prefiero mover la definición fuera de la declaración.

EDIT: debería mencionar que no puedo volver a utilizar enable_if < ...> en la definición, porque enable_if < ...> asigna un valor predeterminado para su tipo, que no se puede hacer en una definición que no es también una declaración.

+1

lo que realmente necesita SFINAE para esto? Si declara el segundo constructor como 'plantilla Foo (U primero, U último);', el primer constructor seguirá siendo seleccionado si la persona que llama pasa dos objetos de tipo 'T'. –

+0

Tipo T es generalmente un tipo aritmético, y quiero poder pasar ints cuando T no está firmado y viceversa, y no tener el constructor con plantilla llamado (lo que estaba sucediendo antes de usar el enable_if) – swestrup

+0

En realidad, no estás asignando un valor predeterminado en absoluto. El segundo parámetro para su plantilla es 'enable_if >'. Más o menos como si esperaras un 'int'. No debería compilarse y ciertamente sería imposible de usar. –

Respuesta

3

No lo haría de esa manera. He aquí los cambios que haría:

template <class T> 
struct Foo 
    { 
    Foo(T a, T b); 

    template 
     < class Iterator 
     > 
    Foo 
     (Iterator first 
     , Iterator last 
     , typename enable_if<is_iterator<Iterator> >::type* = 0 
    ); 
    }; 

template <class T> 
Foo<T>::Foo(T a, T b) 
{ ... } 

template <class T> 
template 
    < class U 
    > 
Foo<T>::Foo(U f, U l, typename enable_if< is_iterator<U> >::type*) 
{ ... } 

Esto es directamente de la documentación para enable_if.

+0

Interesante, he leído varias veces la documentación de enable_if, y nunca se me ocurrió tratar a un constructor como una función anidada ... Si esto funciona, entonces es definitivamente aceptable. – swestrup

+0

Ver http://www.boost.org/doc/libs/1_46_0/libs/utility/enable_if.html sección 3: "Los constructores y los destructores no tienen un tipo de devolución; un argumento adicional es la única opción." –

+0

¡Gracias! Eso efectivamente hizo el truco. ¿Eso significa que lo que estaba intentando antes era imposible, en el sentido de que no hay forma de dar una firma de tipo que habría satisfecho a C++? – swestrup

2

¿Es esto lo que estás tratando de lograr? [No tengo un rasgo de tipo is_iterator, así que he reelaborado su ejemplo utilizando los rasgos de tipo C++ 0x y las bibliotecas de utilidades. Debería funcionar de la misma manera con las bibliotecas TR1 y Boost.]

#include <utility> 
#include <type_traits> 

template <typename T> 
struct S 
{ 
    // Constructor (1) 
    S(T, T); 

    // Constructor (2) 
    template <typename U> 
    S(U, U, typename std::enable_if<std::is_integral<U>::value>::type* = 0); 
}; 

template <typename T> 
S<T>::S(T, T) 
{ } 

template <typename T> 
template <typename U> 
S<T>::S(U, U, typename std::enable_if<std::is_integral<U>::value>::type*) 
{ } 

int main() 
{ 
    S<double> a(1.0, 2.0); // uses (1) 
    S<double> b(1, 2);  // uses (2) 
} 
+0

Sí, eso es más o menos correcto. Tuve que escribir mi propio is_iterator. No tengo idea de por qué no es estándar en el impulso. – swestrup

1

El más simple que puede hacer es:

template<class Iterator> 
Foo 
    (Iterator first 
    , typename enable_if<is_iterator<Iterator>, Iterator>::type last 
); 
1
template <class T> 
struct Foo 
    { 
    Foo(T a, T b); 

    template <class Iterator 
     ,  class = typename std::enable_if 
         <is_iterator<Iterator>::value> 
         ::type 
     > 
    Foo 
     (Iterator first 
     , Iterator last 
    ); 
    }; 

template <class T> 
Foo<T>::Foo(T a, T b) 
{ } 

template <class T> 
template 
    < class U 
    , class > 
Foo<T>::Foo(U f, U l) 
{ } 
+0

Lo he intentado y no funciona. Recibo un mensaje que dice que la definición no coincide con ninguna declaración en mi plantilla. Aunque, debo decir, ¡esto ** debería ** funcionar! – swestrup

+0

Lo probé con g ++ - 4.4 y clang. Sin embargo, tuve -std = C++ 0x para mis pruebas y ahora que lo compruebo, creo que es necesario. Sin él, clang da esta advertencia: advertencia: los argumentos de plantilla predeterminados para una plantilla de función son una extensión de C++ 0x [-WC++ 0x-extensions] , class = typename std :: enable_if –

+0

Extraño, su ejemplo SÍ funciona, cuando Lo intento con mi compilador. Pero cuando intento lo mismo en una plantilla real, aparece un error. Debo estar haciendo algo mal ... – swestrup

Cuestiones relacionadas