ultima compiler compilador c++ templates g++ c++11

c++ - compilador - gnu g++ compiler



std:: enable_if para compilar condicionalmente una funciĆ³n miembro (6)

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 compila con gcc 4.7 y después de horas y horas de intentarlo les pregunto cuál es mi error.

#include <utility> #include <iostream> template< class T > class Y { public: template < typename = typename std::enable_if< true >::type > T foo() { return 10; } template < typename = typename std::enable_if< false >::type > T foo() { return 10; } }; int main() { Y< double > 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<false>'' does not name a type enable_if.cpp:13:15: error: `template<class T> template<class> T Y::foo()'' cannot be overloaded enable_if.cpp:9:15: error: with `template<class T> template<class> T Y::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< bool, T = void >::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.


De this publicación:

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

Pero uno puede hacer algo como esto:

#include <iostream> struct Foo { template < class T, class std::enable_if < !std::is_integral<T>::value, int >::type = 0 > void f(const T& value) { std::cout << "Not int" << std::endl; } template<class T, class std::enable_if<std::is_integral<T>::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 }


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<T, double>::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<T, double>::value) T foo() { return 10; } TICK_MEMBER_REQUIRES(!std::is_same<T, double>::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<long N> 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


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

#include <iostream> #include <type_traits> class foo; class bar; template<class T> struct check { template<class Q = T> typename std::enable_if<std::is_same<Q, bar>::value, bool>::type test() { return true; } template<class Q = T> typename std::enable_if<!std::is_same<Q, bar>::value, bool>::type test() { return false; } }; int main() { check<foo> check_foo; check<bar> 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 here .


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

#include <utility> #include <iostream> 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; }

Compilar con:

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

Correr da:

./a.out 11


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<int> 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<int> que contiene

class Y<int> { 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<false>::type accede a un std::enable_if<false>::type no existente, por lo que la declaración está mal formada. Y así tu programa 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.


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<class T> struct FooImpl<T, true> { T foo() { return 10; } }; template<class T> struct FoolImpl<T,false> { T foo() { return 5; } }; template< class T > class Y : public FooImpl<T, boost::is_integer<T> > // whatever your test is goes here. { public: typedef FooImpl<T, boost::is_integer<T> > 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 T, bool condition> class Goo; // repeat pattern above. template<class T, bool condition> class Foo<T, true> : public Goo<T, boost::test<T> > { public: typedef Goo<T, boost::test<T> > inherited: // etc. etc. };