std :: enable_if para comstackr condicionalmente una función miembro

Estoy tratando de obtener un ejemplo simple para entender cómo usar std::enable_if . Después de leer esta respuesta , pensé que no debería ser demasiado difícil encontrar un ejemplo simple. Deseo usar std::enable_if para elegir entre dos funciones miembro y permitir que solo se use una de ellas.

Desafortunadamente, lo siguiente no comstack con gcc 4.7 y después de horas y horas de intentarlo les pregunto cuál es mi error.

 #include  #include  template class Y { public: template < typename = typename std::enable_if::type > T foo() { return 10; } template < typename = typename std::enable_if::type > T foo() { return 10; } }; int main() { Y y; std::cout << y.foo() << std::endl; } 

gcc informa los siguientes problemas:

 % LANG=C make CXXFLAGS="-std=c++0x" enable_if g++ -std=c++0x enable_if.cpp -o enable_if enable_if.cpp:12:65: error: `type' in `struct std::enable_if' does not name a type enable_if.cpp:13:15: error: `template template TY::foo()' cannot be overloaded enable_if.cpp:9:15: error: with `template template TY::foo()' 

¿Por qué no elimina g ++ la creación de instancias incorrecta para la función del segundo miembro? De acuerdo con el estándar, std::enable_if::type solo existe cuando el parámetro boolean template es verdadero. Pero, ¿por qué g ++ no considera esto como SFINAE? Creo que el mensaje de error de sobrecarga proviene del problema de que g ++ no elimina la función de segundo miembro y cree que esto debería ser una sobrecarga.

SFINAE solo funciona si la sustitución en argumento de deducción de un argumento de plantilla hace que la construcción esté mal formada. No hay tal sustitución.

Pensé en eso también e intenté usar std::is_same< T, int >::value y ! std::is_same< T, int >::value ! std::is_same< T, int >::value que da el mismo resultado.

Esto se debe a que cuando se crea una instancia de la plantilla de clase (lo que sucede cuando se crea un objeto de tipo Y entre otros casos), se crean todas las declaraciones de miembros (¡no necesariamente sus definiciones / cuerpos!). Entre ellos también están sus plantillas de miembros. Tenga en cuenta que T se conoce entonces, y !std::is_same< T, int >::value da como resultado falso. Por lo tanto, creará una clase Y que contiene

 class Y { public: /* instantiated from template < typename = typename std::enable_if< std::is_same< T, int >::value >::type > T foo() { return 10; } */ template < typename = typename std::enable_if< true >::type > int foo(); /* instantiated from template < typename = typename std::enable_if< ! std::is_same< T, int >::value >::type > T foo() { return 10; } */ template < typename = typename std::enable_if< false >::type > int foo(); }; 

El tipo std::enable_if::type accede a un std::enable_if::type no existente, por lo que la statement está mal formada. Y así tu progtwig no es válido.

enable_if hacer que las plantillas de miembro ‘ enable_if dependan de un parámetro de la plantilla de miembro. Entonces las declaraciones son válidas, porque todo el tipo sigue siendo dependiente. Cuando intentas llamar a uno de ellos, se produce una deducción de argumentos para sus argumentos de plantilla y SFINAE sucede como se esperaba. Vea esta pregunta y la respuesta correspondiente sobre cómo hacer eso.

Hice este pequeño ejemplo que también funciona.

 #include  #include  class foo; class bar; template struct check { template typename std::enable_if::value, bool>::type test() { return true; } template typename std::enable_if< !std::is_same::value, bool>::type test() { return false; } }; int main() { check check_foo; check check_bar; if (!check_foo.test() && check_bar.test()) std::cout < < "It works!" << std::endl; return 0; } 

Comenta si quieres que elabore más. Creo que el código es más o menos autoexplicativo, pero de nuevo lo hice para que pueda estar equivocado 🙂

Puedes verlo en acción aquí .

Para aquellos que llegan tarde que buscan una solución que “simplemente funciona”:

 #include  #include  template< typename T > class Y { template< bool cond, typename U > using resolvedType = typename std::enable_if< cond, U >::type; public: template< typename U = T > resolvedType< true, U > foo() { return 11; } template< typename U = T > resolvedType< false, U > foo() { return 12; } }; int main() { Y< double > y; std::cout < < y.foo() << std::endl; } 

Comstackr con:

 g++ -std=gnu++14 test.cpp 

Correr da:

 ./a.out 11 

De esta publicación:

Los argumentos de plantilla predeterminados no son parte de la firma de una plantilla

Pero uno puede hacer algo como esto:

 #include  struct Foo { template < class T, class std::enable_if < !std::is_integral::value, int >::type = 0 > void f(const T& value) { std::cout < < "Not int" << std::endl; } template::value, int>::type = 0> void f(const T& value) { std::cout < < "Int" << std::endl; } }; int main() { Foo foo; foo.f(1); foo.f(1.1); // Output: // Int // Not int } 

Una forma de resolver este problema, la especialización de las funciones miembro es poner la especialización en otra clase, y luego heredar de esa clase. Puede que tenga que cambiar el orden de la herencia para tener acceso a todos los demás datos subyacentes, pero esta técnica funciona.

 template< class T, bool condition> struct FooImpl; template struct FooImpl { T foo() { return 10; } }; template struct FoolImpl { T foo() { return 5; } }; template< class T > class Y : public FooImpl > // whatever your test is goes here. { public: typedef FooImpl > inherited; // you will need to use "inherited::" if you want to name any of the // members of those inherited classes. }; 

La desventaja de esta técnica es que si necesita probar muchas cosas diferentes para diferentes funciones de miembro, deberá crear una clase para cada una y encadenarla en el árbol de herencia. Esto es cierto para acceder a los miembros de datos comunes.

Ex:

 template class Goo; // repeat pattern above. template class Foo : public Goo > { public: typedef Goo > inherited: // etc. etc. }; 

El booleano necesita depender del parámetro de la plantilla que se deduce. Entonces, una manera fácil de corregir es usar un parámetro booleano predeterminado:

 template< class T > class Y { public: template < bool EnableBool = true, typename = typename std::enable_if<( std::is_same::value && EnableBool )>::type > T foo() { return 10; } }; 

Sin embargo, esto no funcionará si desea sobrecargar la función miembro. En su lugar, es mejor utilizar TICK_MEMBER_REQUIRES de la biblioteca Tick :

 template< class T > class Y { public: TICK_MEMBER_REQUIRES(std::is_same::value) T foo() { return 10; } TICK_MEMBER_REQUIRES(!std::is_same::value) T foo() { return 10; } }; 

También puede implementar su propio miembro requiere macro como esta (en caso de que no desee utilizar otra biblioteca):

 template struct requires_enum { enum class type { none, all }; }; #define MEMBER_REQUIRES(...) \ typename requires_enum<__line__>::type PrivateRequiresEnum ## __LINE__ = requires_enum<__line__>::type::none, \ class=typename std::enable_if< ((PrivateRequiresEnum ## __LINE__ == requires_enum<__LINE__>::type::none) && (__VA_ARGS__))>::type