c++ - ¿Cómo usar sfinae para seleccionar constructores?
c++11 (4)
La respuesta aceptada es buena para la mayoría de los casos, pero falla si hay dos sobrecargas de constructor con condiciones diferentes. Estoy buscando una solución en ese caso también.
Sí: la solución aceptada funciona pero no para dos constructores alternativos como, por ejemplo,
template <int otherN, typename = typename std::enable_if<otherN == 1>::type>
explicit A(A<otherN> const &);
template <int otherN, typename = typename std::enable_if<otherN != 1>::type>
explicit A(A<otherN> const &);
Porque, como se indica en esta página ,
Un error común es declarar dos plantillas de función que difieren solo en sus argumentos de plantilla predeterminados. Esto es ilegal porque los argumentos de la plantilla predeterminada no forman parte de la firma de la plantilla de función, y declarar dos plantillas de función diferentes con la misma firma es ilegal.
Como se propone en la misma página, puede solucionar este problema aplicando SFINAE, modificando la firma, al tipo de parámetro de plantilla de valor (no tipo) de la siguiente manera
template <int otherN, typename std::enable_if<otherN == 1, bool>::type = true>
explicit A(A<otherN> const &);
template <int otherN, typename std::enable_if<otherN != 1, bool>::type = true>
explicit A(A<otherN> const &);
En la meta programación de plantillas, se puede usar SFINAE en el tipo de retorno para elegir una determinada función de miembro de plantilla, es decir,
template<int N> struct A {
int sum() const noexcept
{ return _sum<N-1>(); }
private:
int _data[N];
template<int I> typename std::enable_if< I,int>::type _sum() const noexcept
{ return _sum<I-1>() + _data[I]; }
template<int I> typename std::enable_if<!I,int>::type _sum() const noexcept
{ return _data[I]; }
};
Sin embargo, esto no funciona en los constructores. Supongamos, quiero declarar al constructor.
template<int N> struct A {
/* ... */
template<int otherN>
explicit(A<otherN> const&); // only sensible if otherN >= N
};
pero no lo otherN < N
para otherN < N
.
Entonces, ¿ se puede usar SFINAE aquí? Solo me interesan las soluciones que permiten la deducción automática de parámetros de plantilla, de modo que
A<4> a4{};
A<5> a5{};
A<6> a6{a4}; // doesn''t compile
A<3> a3{a5}; // compiles and automatically finds the correct constructor
Nota: este es un ejemplo muy simplificado donde SFINAE puede ser una exageración y static_assert
puede ser suficiente. Sin embargo, quiero saber si puedo usar SFINAE en su lugar.
En C ++ 11, puede usar un parámetro de plantilla predeterminado:
template <int otherN, class = typename std::enable_if<otherN >= N>::type>
explicit A(A<otherN> const &);
Sin embargo, si su compilador todavía no admite parámetros de plantilla predeterminados, o si necesita varias sobrecargas, entonces puede usar un parámetro de función predeterminado como este:
template <int otherN>
explicit A(A<otherN> const &, typename std::enable_if<otherN >= N>::type* = 0);
Hay muchas formas de activar SFINAE, siendo enable_if
solo una de ellas. Ante todo:
¿Qué es std :: enable_if?
Es solo esto:
template<bool, class T=void> enable_if{ typedef T type; };
template<class T> enable_if<false,T> {};
template<bool b, class T=void> using enable_if_t = typename enable_f<b,T>::type;
La idea es hacer que typename enable_if<false>::type
sea un error, por lo tanto, haga que se typename enable_if<false>::type
cualquier declaración de plantilla que lo contenga.
Entonces, ¿cómo puede esta función de activación de selección?
Funciones de desactivacion
La idea es hacer la declaración errónea en alguna parte:
Por tipo de devolución
template<class Type>
std::enable_if_t<cond<Type>::value,Return_type> function(Type);
Por un parámetro real
template<class Type>
return_type function(Type param, std::enable_if_t<cond<Type>::value,int> =0)
Por un parámetro de plantilla
template<class Type,
std::enable_if_t<cond<Type>::value,int> =0> //note the space between > and =
return_type function(Type param)
Seleccionando funciones
Puedes parametrizar diferentes alternativas con trucos como este:
tempplate<int N> struct ord: ord<N-1>{};
struct ord<0> {};
template<class T, std::enable_if<condition3, int> =0>
retval func(ord<3>, T param) { ... }
template<class T, std::enable_if<condition2, int> =0>
retval func(ord<2>, T param) { ... }
template<class T, std::enable_if<condition1, int> =0>
retval func(ord<1>, T param) { ... }
template<class T> // default one
retval func(ord<0>, T param) { ... }
// THIS WILL BE THE FUCNTION YOU''LL CALL
template<class T>
retval func(T param) { return func(ord<9>{},param); } //any "more than 3 value"
Esto llamará a la primera / segunda / tercera / cuarta función si se cumple la condition2
, que condition2
que condition1
que ninguna de ellas.
Otros desencadenantes SFINAE
Escribir las condiciones de tiempo de compilación puede ser una cuestión de especialización explícita o una cuestión de éxito / fracaso de una evaluación no evaluada:
por ejemplo:
template<class T, class = void>
struct is_vector: std::false_type {};
template<class X>
struct is_vector<vector<X> >:: std::true_type {};
de modo que is_vector<int>::value
es false
pero is_vecttor<vector<int> >::value
es true
O, por medio de la introspección , como
template<class T>
struct is_container<class T, class = void>: std::false_type {};
template<class T>
struct is_container<T, decltype(
std::begin(std::declval<T>()),
std::end(std::declval<T>()),
std::size(std::declval<T>()),
void(0))>: std::true_type {};
de modo que is_container<X>::value
será true
si se le da X x
, puede compilar std::begin(x)
etc.
El truco es que el decltype(...)
es en realidad void
(el operador ,
descarta las expresiones anteriores) solo si todas las sub-expresiones son compilables.
Incluso puede haber muchas otras alternativas. Espero que entre todo esto puedas encontrar algo útil.
Puede agregar un argumento de tipo predeterminado a la plantilla:
template <int otherN, typename = typename std::enable_if<otherN >= N>::type>
explicit A(A<otherN> const &);