2012-07-04 22 views
8

Tengo una SFINAE problema:SFINAE: Compilador no recoge la clase de plantilla especializada

En el siguiente código, quiero que el compilador de C++ para recoger el funtor especializado y de impresión "especial", pero es la impresión "en general "en cambio".

#include <iostream> 
#include <vector> 

template<class T, class V = void> 
struct Functor { 
    void operator()() const { 
    std::cerr << "general" << std::endl; 
    } 
}; 

template<class T> 
struct Functor<T, typename T::Vec> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

struct Foo { 
    typedef std::vector<int> Vec; 
}; 

int main() { 
    Functor<Foo> ac; 
    ac(); 
} 

¿Cómo puedo solucionarlo para que la estructura especializada se use automáticamente? Tenga en cuenta que no quiero especializar directamente la estructura Functor en Foo, pero quiero especializarla en todos los tipos que tienen un tipo Vec.

P.S .: Estoy usando g ++ 4.4.4

+0

Se ha eliminado la etiqueta 'compiler', generalmente se utiliza para la pregunta sobre el proceso de compilación en sí, mientras que esta pregunta es sobre el lenguaje C++. –

Respuesta

11

Lo siento por usted engañosa en la última respuesta, pensé por un momento que sería más sencillo. Entonces intentaré proporcionar una solución completa aquí. El enfoque general para resolver este tipo de problemas es escribir una plantilla rasgos ayudante y utilizarlo junto con enable_if (ya sea C++ 11, impulso o aplicación manual) para decidir una especialización de clase:

Rasgo

Un enfoque simple, no necesariamente la mejor, pero fácil de escribir sería:

template <typename T> 
struct has_nested_Vec { 
    typedef char yes; 
    typedef char (&no)[2]; 
    template <typename U> 
    static yes test(typename U::Vec* p); 
    template <typename U> 
    static no test(...); 

    static const bool value = sizeof(test<T>(0)) == sizeof(yes); 
}; 

el enfoque es simple, proporciona dos funciones de plantilla, que los tipos de retorno de diferentes tamaños. Uno de los cuales toma el tipo anidado Vec y el otro toma puntos suspensivos. Para todos los tipos que tienen un Vec anidado, la primera sobrecarga es una mejor coincidencia (la elipsis es la peor coincidencia para cualquier tipo). Para aquellos tipos que no tienen un Vec anidado, SFINAE descartará esa sobrecarga y la única opción que queda será la elipsis. Entonces ahora tenemos un rasgo para preguntar si cualquier tipo tiene un tipo anidado Vec.

enable Si

Se puede usar esta de cualquier biblioteca, o se puede liar, es bastante simple:

template <bool state, typename T = void> 
struct enable_if {}; 

template <typename T> 
struct enable_if<true,T> { 
    typedef T type; 
}; 

Cuando el primer argumento es false, la plantilla base es la única opción, y que no tiene un type anidado, si la condición es true, entonces enable_if tiene un type anidado que podemos usar con SFINAE.

Implementación

Ahora necesitamos proveer la plantilla y la especialización que va a utilizar SFINAE sólo para aquellos tipos con un anidado Vec:

template<class T, class V = void> 
struct Functor { 
    void operator()() const { 
     std::cerr << "general" << std::endl; 
    } 
}; 
template<class T> 
struct Functor<T, typename enable_if<has_nested_Vec<T>::value>::type > { 
    void operator()() const { 
     std::cerr << "special" << std::endl; 
    } 
}; 

Cada vez que creamos una instancia Functor con un tipo, el el compilador intentará usar la especialización, que a su vez instanciará has_nested_Vec y obtendrá un valor de verdad, pasado a enable_if. Para aquellos tipos cuyo valor es false, enable_if no tiene un tipo anidado type, por lo que la especialización se descartará en SFINAE y se utilizará la plantilla base.

su caso particular

En su caso particular, en el que parece que realmente no necesita especializarse todo tipo, pero sólo el operador, se pueden mezclar los tres elementos en uno solo: una Functor que despacha a una de las dos funciones con plantilla interna en base a la presencia de Vec, eliminando la necesidad de enable_if y la clase rasgos:

template <typename T> 
class Functor { 
    template <typename U> 
    void op_impl(typename U::Vec* p) const { 
     std::cout << "specialized"; 
    } 
    template <typename U> 
    void op_impl(...) const { 
     std::cout << "general"; 
    } 
public: 
    void operator()() const { 
     op_impl<T>(0); 
    } 
}; 
2

a pesar de que esta es una vieja pregunta, creo que es todavía vale la pena proporcionar un par más alternativas para arreglar rápidamente el código original.

Básicamente, el problema no es con el uso de SFINAE (esa parte está muy bien, en realidad), pero con la igualar el parámetro de defecto en la plantilla primaria (void) para el argumento suministrado en la especialización parcial (typename T::Vec) . Debido al parámetro predeterminado en la plantilla primaria, Functor<Foo> en realidad significa Functor<Foo, void>. Cuando el compilador intenta crear una instancia de que el uso de la especialización, intenta hacer coincidir los dos argumentos con los de la especialización y no, como void no puede ser sustituido por std::vector<int>. Luego vuelve a instanciar usando la plantilla primaria.

Por lo tanto, la solución más rápida, que asume todos sus Vec s son std::vector<int> s, es reemplazar la línea

template<class T, class V = void> 

con este

template<class T, class E = std::vector<int>> 

Ahora se utilizará La especialización, debido a que la los argumentos coincidirán. Simple, pero muy limitante. Claramente, necesitamos controlar mejor el tipo de argumento en la especialización, para que coincida con algo que podamos especificar como el parámetro predeterminado en la plantilla primaria. Una solución rápida que no requiere la definición de nuevos rasgos es la siguiente:

#include <iostream> 
#include <vector> 
#include <type_traits> 

template<class T, class E = std::true_type> 
struct Functor { 
    void operator()() const { 
    std::cerr << "general" << std::endl; 
    } 
}; 

template<class T> 
struct Functor<T, typename std::is_reference<typename T::Vec&>::type> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

struct Foo { 
    typedef std::vector<int> Vec; 
}; 

int main() { 
    Functor<Foo> ac; 
    ac(); 
} 

Esto funcionará para cualquier tipo Vec que podría tener sentido aquí, incluyendo tipos fundamentales y matrices, por ejemplo, y las referencias o punteros a ellos.

1

Otra alternativa para detectar la existencia de un tipo de miembro es usar void_t. Como las especializaciones parciales válidas son preferibles a la implementación general, siempre que coincidan con los parámetros predeterminados, queremos un tipo que se evalúe como void cuando sea válido, y solo es válido cuando el miembro especificado exista; este tipo es comúnmente (y, a partir de C++ 17, canónicamente) conocido como void_t.

template<class...> 
using void_t = void; 

Si el compilador no admite correctamente (en los primeros compiladores de C++ 14, los parámetros utilizados en las plantillas de alias no se les garantizó a garantizar SFINAE, rompiendo el anterior void_t), una solución disponible.

template<typename... Ts> struct make_void { typedef void type; }; 
template<typename... Ts> using void_t = typename make_void<Ts...>::type; 

Como de C++ 17, void_t se encuentra disponible en la biblioteca de servicios públicos, en type_traits.

#include <iostream> 
#include <vector> 
#include <type_traits> // For void_t. 

template<class T, class V = void> 
struct Functor { 
    void operator()() const { 
    std::cerr << "general" << std::endl; 
    } 
}; 

// Use void_t here. 
template<class T> 
struct Functor<T, std::void_t<typename T::Vec>> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

struct Foo { 
    typedef std::vector<int> Vec; 
}; 

int main() { 
    Functor<Foo> ac; 
    ac(); 
} 

Con esto, la salida es special, como se pretende.


En este caso, ya que estamos la comprobación de la existencia de un tipo de miembro, el proceso es muy simple; se puede hacer sin la expresión SFINAE o la biblioteca type_traits, lo que nos permite reescribir el cheque para usar las instalaciones de C++ 03 si es necesario.

// void_t: 
// Place above Functor's definition. 
template<typename T> struct void_t { typedef void type; }; 

// ... 

template<class T> 
struct Functor<T, typename void_t<typename T::Vec>::type> { 
    void operator()() const { 
    std::cerr << "special" << std::endl; 
    } 
}; 

Que yo sepa, esto debería funcionar en la mayoría, si no todos, SFINAE con capacidad C++ 03-, 11- C++, C++ 14-, o C++ 1z compatibles con los compiladores . Esto puede ser útil cuando se trata de compiladores que se retrasan un poco en el estándar, o cuando se compilan para plataformas que aún no tienen compiladores compatibles con C++ 11.


Para obtener más información sobre void_t, ver cppreference.

Cuestiones relacionadas