web-dev-qa-db-fra.com

std :: enable_if pour compiler de manière conditionnelle une fonction membre

J'essaie d'obtenir un exemple simple pour comprendre comment utiliser std::enable_if. Après avoir lu cette réponse , j'ai pensé qu'il ne devrait pas être trop difficile de trouver un exemple simple. Je veux utiliser std::enable_if pour choisir entre deux fonctions membres et n’autoriser que l’une d’entre elles.

Malheureusement, ce qui suit ne compile pas avec gcc 4.7 et après des heures et des heures d’essai, je vous demande quelle est mon erreur.

#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 rapporte les problèmes suivants:

% 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()'

Pourquoi g ++ ne supprime-t-il pas la mauvaise instanciation pour la deuxième fonction membre? Selon la norme, std::enable_if< bool, T = void >::type n'existe que lorsque le paramètre de modèle booléen est true. Mais pourquoi g ++ ne considère-t-il pas cela comme SFINAE? Je pense que le message d'erreur de surcharge vient du problème que g ++ ne supprime pas la deuxième fonction membre et pense que cela devrait être une surcharge.

136
evnu

SFINAE ne fonctionne que si la substitution d'argument par déduction d'un argument de modèle rend la construction mal formée. Il n'y a pas une telle substitution.

J'y ai pensé aussi et j'ai essayé d'utiliser std::is_same< T, int >::value et ! std::is_same< T, int >::value qui donne le même résultat.

En effet, lorsque le modèle de classe est instancié (ce qui se produit lorsque vous créez un objet de type Y<int> _ entre autres), il instancie toutes ses déclarations de membres (pas nécessairement leurs définitions/corps!). Parmi eux se trouvent également ses modèles de membres. Notez que T est alors connu, et !std::is_same< T, int >::value donne faux. Donc, cela va créer une classe Y<int> qui contient

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();
};

Le std::enable_if<false>::type accède à un type inexistant, de sorte que la déclaration est mal formée. Et donc votre programme est invalide.

Vous devez définir les modèles de membre 'enable_if dépendent d'un paramètre du modèle de membre lui-même. Ensuite, les déclarations sont valides, car le type entier est toujours dépendant. Lorsque vous essayez d'appeler l'un d'entre eux, la déduction d'argument pour leurs arguments de modèle se produit et SFINAE se déroule comme prévu. Voir cette question et la réponse correspondante sur la façon de le faire.

106

J'ai fait ce court exemple qui fonctionne aussi.

#include <iostream>
#include <type_traits>

class foo;
class bar;

template<class T>
struct is_bar
{
    template<class Q = T>
    typename std::enable_if<std::is_same<Q, bar>::value, bool>::type check()
    {
        return true;
    }

    template<class Q = T>
    typename std::enable_if<!std::is_same<Q, bar>::value, bool>::type check()
    {
        return false;
    }
};

int main()
{
    is_bar<foo> foo_is_bar;
    is_bar<bar> bar_is_bar;
    if (!foo_is_bar.check() && bar_is_bar.check())
        std::cout << "It works!" << std::endl;

    return 0;
}

Commentez si vous voulez que je développe. Je pense que le code est plus ou moins explicite, mais encore une fois, je l’ai fait pour que je puisse me tromper :)

Vous pouvez le voir en action ici .

75
jpihl

Pour les retardataires à la recherche d'une solution qui "fonctionne":

#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;
}

Compiler avec:

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

Courir donne:

./a.out 
11
13
user1284631

De this post:

Les arguments de modèle par défaut ne font pas partie de la signature d'un modèle

Mais on peut faire quelque chose comme ça:

#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
}
7
Janek Olszak

Une façon de résoudre ce problème, la spécialisation des fonctions membres, consiste à placer la spécialisation dans une autre classe, puis à hériter de cette classe. Vous devrez peut-être modifier l'ordre d'héritage pour avoir accès à toutes les autres données sous-jacentes, mais cette technique fonctionne.

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.
};

L'inconvénient de cette technique est que si vous devez tester différentes choses pour différentes fonctions de membre, vous devez créer une classe pour chaque classe et la chaîner dans l'arborescence d'héritage. Cela est vrai pour accéder aux membres de données communs.

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
Gary Powell

Le booléen doit dépendre du paramètre de modèle à déduire. Donc, un moyen facile de réparer consiste à utiliser un paramètre booléen par défaut:

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;
        }

};

Toutefois, cela ne fonctionnera pas si vous souhaitez surcharger la fonction membre. Au lieu de cela, il vaut mieux utiliser TICK_MEMBER_REQUIRES de la bibliothèque 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;
        }

};

Vous pouvez aussi implémenter votre propre membre requiert une macro comme celle-ci (juste au cas où vous ne voudriez pas utiliser une autre bibliothèque):

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
4
Paul Fultz II