182 votes

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

Je suis en train d'essayer d'obtenir un exemple simple de travailler à comprendre comment utiliser l' std::enable_if. Après j'ai lu cette réponse, j'ai pensé qu'il ne devrait pas être trop difficile à venir avec un exemple simple. Je veux utiliser std::enable_if de choisir entre deux membres de fonctions et de permettre à un seul d'entre eux à être utilisé.

Malheureusement, la suite n'est pas compilé avec gcc 4.7, et après des heures et des heures à essayer, je vous demande de gars que mon erreur est.

#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 rapports sur 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 ne pas g++ suppression erronée de l'instanciation de la deuxième fonction de membre? Selon la norme, std::enable_if< bool, T = void >::type n'existe que lorsque le modèle booléen paramètre est true. Mais pourquoi ne pas g++ considérer cela comme SFINAE? Je pense que la surcharge de message d'erreur vient du problème que g++ n'est pas de supprimer la seconde fonction de membre et estime que cela devrait être une surcharge.

130voto

Johannes Schaub - litb Points 256113

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

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

Parce que lorsque le modèle de classe est instanciée (ce qui arrive lorsque vous créez un objet de type Y<int> parmi les autres cas), il instancie toutes ses déclarations de membre (pas nécessairement leurs définitions/corps!). D'entre eux sont également ses modèles de membre. Notez que T est alors connu, et !std::is_same< T, int >::value rendements faux. Donc, il 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();
};

L' std::enable_if<false>::type accède à un non-existant type, de sorte que la déclaration est mal formé. Et ainsi, votre programme n'est pas valide.

Vous avez besoin de faire les modèles de membre' enable_if dépendent d'un paramètre du membre modèle lui-même. Les déclarations sont valables, parce que le type entier est encore dépendant. Lorsque vous essayez d'appeler l'un d'entre eux, l'argument de la déduction pour leurs arguments de modèle se produire et SFINAE se passe comme prévu. Voir cette question et la réponse correspondante sur la façon de le faire.

112voto

jpihl Points 1034

J'ai fait ce court exemple qui fonctionne aussi.

 #include <iostream>
#include <type_traits>

using namespace std;

class A;
class B;

template<class T>
class C
{
    public:
        template<class Q = T>
        typename std::enable_if<std::is_same<Q, B>::value, bool>::type
        c()
        {
            return true;
        }

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

int main() {
    C<A> ca;
    C<B> cb;
    if (!ca.c() && cb.c())
        cout << "It works!" << 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 :)

11voto

Janek Olszak Points 328

De ce 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
}
 

5voto

Gary Powell Points 31

Une façon de résoudre ce problème, la spécialisation des fonctions de membre est de mettre la spécialisation dans une autre classe, alors héritent de cette classe. Vous pourriez avoir à changer l'ordre de l'héritage pour obtenir l'accès à toutes les autres données sous-jacentes, mais cette technique ne fonctionne pas.

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 avez besoin de tester un grand nombre de différentes choses pour différentes fonctions de membre vous aurez à faire une classe pour chacun, et de la chaîne dans l'héritage de l'arbre. Cela est vrai pour l'accès à des données communes membres.

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

4voto

Paul Points 4552

Le booléen doit dépendre du modèle paramètre être déduite. Donc, une façon simple de résoudre consiste à utiliser une valeur par défaut paramètre booléen:

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 voulez surcharger la fonction de membre. Au lieu de cela, de son mieux pour utiliser TICK_MEMBER_REQUIRES de la Tique de la bibliothèque:

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 également mettre en place votre propre membre exige macro comme ceci(juste au cas où vous ne souhaitez 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X