C ++ SFINAE ejemplos?

Quiero entrar en más meta-progtwigción de plantillas. Sé que SFINAE significa “falla de sustitución no es un error”. ¿Pero alguien puede mostrarme un buen uso para SFINAE?

Aquí hay un ejemplo ( de aquí ):

template class IsClassT { private: typedef char One; typedef struct { char a[2]; } Two; template static One test(int C::*); // Will be chosen if T is anything except a class. template static Two test(...); public: enum { Yes = sizeof(IsClassT::test(0)) == 1 }; enum { No = !Yes }; }; 

Cuando IsClassT::Yes se evalúa, 0 no se puede convertir a int int::* porque int no es una clase, por lo que no puede tener un puntero de miembro. Si SFINAE no existiera, entonces obtendría un error de comstackción, algo como ‘0 no puede convertirse en puntero de miembro para tipo no de clase int’. En cambio, solo usa el ... formulario que devuelve Dos, y por lo tanto evalúa como falso, int no es un tipo de clase.

Me gusta usar SFINAE para verificar las condiciones booleanas.

 template void div(char(*)[I % 2 == 0] = 0) { /* this is taken when I is even */ } template void div(char(*)[I % 2 == 1] = 0) { /* this is taken when I is odd */ } 

Puede ser bastante útil. Por ejemplo, lo usé para verificar si una lista de inicializadores recostackda usando la coma del operador no es más larga que un tamaño fijo

 template struct Vector { template Vector(MyInitList const& i, char(*)[M < = N] = 0) { /* ... */ } } 

La lista solo se acepta cuando M es menor que N, lo que significa que la lista de inicializadores no tiene demasiados elementos.

La syntax char(*)[C] significa: Puntero a una matriz con elemento tipo char y tamaño C Si C es falso (0 aquí), obtenemos el tipo no válido char(*)[0] , puntero a una matriz de tamaño cero: SFINAE lo hace para que la plantilla se ignore entonces.

Expresado con boost::enable_if , que se parece a esto

 template struct Vector { template Vector(MyInitList const& i, typename enable_if_c< (M <= N)>::type* = 0) { /* ... */ } } 

En la práctica, a menudo encuentro la habilidad de verificar las condiciones como una habilidad útil.

La biblioteca enable_if de Boost ofrece una interfaz limpia y agradable para usar SFINAE. Uno de mis ejemplos de uso favoritos está en la biblioteca Boost.Iterator . SFINAE se usa para habilitar las conversiones de tipo de iterador.

En C ++ 11 las pruebas SFINAE se han vuelto mucho más bonitas. Aquí hay algunos ejemplos de usos comunes:

Elija una sobrecarga de función en función de los rasgos

 template std::enable_if_t::value> f(T t){ //integral version } template std::enable_if_t::value> f(T t){ //floating point version } 

Usando una expresión idiomática del tipo “sink”, puedes hacer pruebas bastante arbitrarias en un tipo como verificar si tiene un miembro y si ese miembro es de cierto tipo

 //this goes in some header so you can use it everywhere template struct TypeSink{ using Type = void; }; template using TypeSinkT = typename TypeSink::Type; //use case template struct HasBarOfTypeInt : std::false_type{}; template struct HasBarOfTypeInt().*(&T::bar))>> : std::is_same().*(&T::bar))>::type,int>{}; struct S{ int bar; }; struct K{ }; template> void print(T){ std::cout < < "has bar" << std::endl; } void print(...){ std::cout << "no bar" << std::endl; } int main(){ print(S{}); print(K{}); std::cout << "bar is int: " << HasBarOfTypeInt::value < < std::endl; } 

Aquí hay un ejemplo en vivo: http://ideone.com/dHhyHE También escribí recientemente una sección completa sobre SFINAE y el despacho de tags en mi blog (enchufe descarado pero relevante) http://metaporky.blogspot.de/2014/08/ part-7-static-dispatch-function.html

Tenga en cuenta que a partir de C ++ 14 hay un std :: void_t que es esencialmente el mismo que mi TypeSink aquí.

Aquí hay otro ejemplo (tardío) de SFINAE , basado en la respuesta de Greg Rogers :

 template class IsClassT { template static bool test(int C::*) {return true;} template static bool test(...) {return false;} public: static bool value; }; template bool IsClassT::value=IsClassT::test(0); 

De esta forma, puede verificar el value del valor para ver si T es una clase o no:

 int main(void) { std::cout < < IsClassT::value < < std::endl; // true std::cout << IsClassT::value < < std::endl; // false return 0; } 

C ++ 17 probablemente proporcionará un medio genérico para consultar características. Consulte N4502 para obtener detalles, pero como ejemplo independiente, considere lo siguiente.

Esta parte es la parte constante, ponla en un encabezado.

 // See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf. template  using void_t = void; // Primary template handles all types not supporting the operation. template  class, typename = void_t<>> struct detect : std::false_type {}; // Specialization recognizes/validates only types supporting the archetype. template  class Op> struct detect>> : std::true_type {}; 

El siguiente ejemplo, tomado de N4502 , muestra el uso:

 // Archetypal expression for assignment operation. template  using assign_t = decltype(std::declval() = std::declval()) // Trait corresponding to that archetype. template  using is_assignable = detect; 

En comparación con las otras implementaciones, esta es bastante simple: un conjunto reducido de herramientas ( void_t y detect ) es suficiente. Además, se informó (ver N4502 ) que es mensurablemente más eficiente (tiempo de comstackción y consumo de memoria del comstackdor) que los enfoques anteriores.

Aquí hay un ejemplo en vivo , que incluye ajustes de portabilidad para GCC pre 5.1.

Aquí hay un buen artículo de SFINAE: Una introducción al concepto SFINAE de C ++: introspección en tiempo de comstackción de un miembro de la clase .

Resumirlo de la siguiente manera:

 /* The compiler will try this overload since it's less generic than the variadic. T will be replace by int which gives us void f(const int& t, int::iterator* b = nullptr); int doesn't have an iterator sub-type, but the compiler doesn't throw a bunch of errors. It simply tries the next overload. */ template  void f(const T& t, typename T::iterator* it = nullptr) { } // The sink-hole. void f(...) { } f(1); // Calls void f(...) { } 

 template // Default template version. struct enable_if {}; // This struct doesn't define "type" and the substitution will fail if you try to access it. template // A specialisation used if the expression is true. struct enable_if { typedef T type; }; // This struct do have a "type" and won't fail on access. template  typename enable_if::value, std::string>::type serialize(const T& obj) { return obj.serialize(); } template  typename enable_if< !hasSerialize::value, std::string>::type serialize(const T& obj) { return to_string(obj); } 

declval es una utilidad que le da una “referencia falsa” a un objeto de un tipo que no podría construirse fácilmente. declval es realmente útil para nuestras construcciones SFINAE.

 struct Default { int foo() const {return 1;} }; struct NonDefault { NonDefault(const NonDefault&) {} int foo() const {return 1;} }; int main() { decltype(Default().foo()) n1 = 1; // int n1 // decltype(NonDefault().foo()) n2 = n1; // error: no default constructor decltype(std::declval().foo()) n2 = n1; // int n2 std::cout < < "n2 = " << n2 << '\n'; }