std:: enable if para compilar condicionalmente una función miembro


Estoy tratando de obtener un ejemplo sencillo para entender cómo usar std::enable_if. Después de leer esta respuesta , pensé que no debería ser demasiado difícil llegar a un ejemplo simple. Quiero 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 lo siguiente 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é g++ no elimina la instanciación incorrecta para la función de segundo miembro? De acuerdo con el estándar, std::enable_if< bool, T = void >::type solo existe cuando el parámetro de plantilla booleana es true. 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 segunda función miembro y cree que esto debería ser una sobrecarga.

Author: Community, 2011-08-07

6 answers

SFINAE solo funciona si la sustitución en la deducción del argumento de una plantilla hace que el constructo esté mal formado. No existe tal sustitución.

También pensé en eso y traté de usar std::is_same< T, int >::value y ! 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 ocurre cuando se crea un objeto de tipo Y<int> entre otros casos), crea una instancia de todas sus declaraciones miembro (¡no necesariamente sus definiciones/cuerpos!). Entre también son sus plantillas de miembros. Tenga en cuenta que T se conoce entonces, y !std::is_same< T, int >::value produce false. Así se 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 std::enable_if<false>::type accede a un tipo inexistente, por lo que la declaración está mal formada. Y por lo tanto su programa no es válido.

Debe hacer que las plantillas miembro' enable_if dependan de un parámetro de la propia plantilla miembro. Entonces las declaraciones son válidas, porque todo el tipo sigue siendo dependiente. Cuando tratas de llamar a uno de ellos, argumento la deducción por sus argumentos de plantilla sucede y SFINAE sucede como se esperaba. Ver esta pregunta y la respuesta correspondiente sobre cómo hacerlo.

 88
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:33

Hice este breve 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;
}

Comente si quiere que le dé más detalles. Creo que el código es más o menos autoexplicativo, pero de nuevo lo hice para que pudiera estar equivocado:)

Puedes verlo en acción aquí.

 53
Author: jpihl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-27 09:12:10

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

#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 

Corriendo da:

./a.out 
11
 10
Author: user1284631,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-30 18:14:51

De este post:

Los argumentos de plantilla predeterminados no forman 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
}
 6
Author: Janek Olszak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-25 10:23:01

Una forma de resolver este problema, la especialización de las funciones miembro es poner la especialización en otra clase, luego heredar de esa clase. Es posible que tenga que cambiar el orden de herencia para obtener 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 miembro, tendrá que hacer una clase para cada una, y encadenarla en el árbol de herencia. Esto es cierto para acceder a 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.
};
 5
Author: Gary Powell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-14 11:13:13

El booleano necesita depender del parámetro de la plantilla que se deduce. Así que una manera fácil de arreglar 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 usar 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 propia macro member requires como esta (en caso de que no desee usar 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
 3
Author: Paul Fultz II,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-30 18:35:06