C++ SFINAE ejemplos?
templates metaprogramming (7)
Quiero entrar en más meta-programació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 otro ejemplo (tardío) de SFINAE , basado en la answer :
template<typename T>
class IsClassT {
template<typename C> static bool test(int C::*) {return true;}
template<typename C> static bool test(...) {return false;}
public:
static bool value;
};
template<typename T>
bool IsClassT<T>::value=IsClassT<T>::test<T>(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<std::string>::value << std::endl; // true
std::cout << IsClassT<int>::value << std::endl; // false
return 0;
}
Aquí hay un buen artículo de SFINAE: Una introducción al concepto SFINAE de C ++: introspección en tiempo de compilació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 <typename T> void f(const T& t, typename T::iterator* it = nullptr) { }
// The sink-hole.
void f(...) { }
f(1); // Calls void f(...) { }
template<bool B, class T = void> // Default template version.
struct enable_if {}; // This struct doesn''t define "type" and the substitution will fail if you try to access it.
template<class T> // A specialisation used if the expression is true.
struct enable_if<true, T> { typedef T type; }; // This struct do have a "type" and won''t fail on access.
template <class T> typename enable_if<hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
return obj.serialize();
}
template <class T> typename enable_if<!hasSerialize<T>::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<NonDefault>().foo()) n2 = n1; // int n2
std::cout << "n2 = " << n2 << ''/n'';
}
Aquí hay un ejemplo ( de aquí ):
template<typename T>
class IsClassT {
private:
typedef char One;
typedef struct { char a[2]; } Two;
template<typename C> static One test(int C::*);
// Will be chosen if T is anything except a class.
template<typename C> static Two test(...);
public:
enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
enum { No = !Yes };
};
Cuando IsClassT<int>::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 compilació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.
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 <typename...>
using void_t = void;
// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};
// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};
El siguiente ejemplo, tomado de N4502 , muestra el uso:
// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())
// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;
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 compilación y consumo de memoria del compilador) que los enfoques anteriores.
Aquí hay un ejemplo en vivo , que incluye ajustes de portabilidad para GCC pre 5.1.
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<typename T>
std::enable_if_t<std::is_integral<T>::value> f(T t){
//integral version
}
template<typename T>
std::enable_if_t<std::is_floating_point<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<typename T>
struct TypeSink{
using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;
//use case
template<typename T, typename=void>
struct HasBarOfTypeInt : std::false_type{};
template<typename T>
struct HasBarOfTypeInt<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>> :
std::is_same<typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type,int>{};
struct S{
int bar;
};
struct K{
};
template<typename T, typename = TypeSinkT<decltype(&T::bar)>>
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<S>::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 etiquetas 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í.
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.
Me gusta usar SFINAE
para verificar las condiciones booleanas.
template<int I> void div(char(*)[I % 2 == 0] = 0) {
/* this is taken when I is even */
}
template<int I> 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 recopilada usando la coma del operador no es más larga que un tamaño fijo
template<int N>
struct Vector {
template<int M>
Vector(MyInitList<M> 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 sintaxis 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<int N>
struct Vector {
template<int M>
Vector(MyInitList<M> 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.