std::activer si conditionnelle, de compiler une fonction membre

j'essaie d'obtenir un exemple simple de travailler pour comprendre comment utiliser les 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 de membre et permettre qu'une seule d'entre elles soit utilisée.

malheureusement, ce qui suit ne se compile pas avec gcc 4.7 et après des heures et des heures d'essai je vous demande ce 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 signale 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 fonction second member? Selon la norme, std::enable_if< bool, T = void >::type n'existe que lorsque le paramètre booléen template 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 fonction du second membre et pense que cela devrait être surcharge.

113
demandé sur Community 2011-08-07 15:02:54

6 réponses

SFINAE ne fonctionne que si la substitution dans l'argument déduction d'un argument de modèle rend le construit mal formé. Il n'y a pas de 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.

c'est parce que lorsque le template de classe est instancié (ce qui arrive quand vous créez un objet de type Y<int> entre autres cas), il instancie toutes les déclarations de ses membres (pas nécessairement leurs définitions/organismes!). Parmi eux, il y a aussi ses modèles de membres. Notez que T est alors connu, et !std::is_same< T, int >::value donne 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();
};

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

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

88
répondu Johannes Schaub - litb 2017-05-23 12:10:33

j'ai fait ce petit exemple qui fonctionne aussi.

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

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

vous pouvez le voir en action ici .

53
répondu jpihl 2016-06-27 09:12:10

pour les retardataires qui cherchent 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 

en cours d'Exécution donne:

./a.out 
11
10
répondu user1284631 2014-09-30 18:14:51

à Partir 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
}
6
répondu Janek Olszak 2014-08-25 10:23:01

une façon de résoudre ce problème, la spécialisation des fonctions des membres est de mettre la spécialisation dans une autre classe, puis hériter de cette classe. Vous devrez peut-être changer l'ordre de succession 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 avez besoin de tester un grand nombre de différentes choses pour différentes fonctions de membre vous aurez à faire une classe pour chacun d'eux, et de la chaîne dans l'héritage de l'arbre. Cela est vrai pour l'accès aux membres de données communes.

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
répondu Gary Powell 2013-04-14 11:13:13

le booléen doit dépendre du paramètre template déduit. Donc une façon facile de corriger est d'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 voulez surcharger la fonction de membre. Au lieu de cela, son meilleur pour utiliser TICK_MEMBER_REQUIRES de la Tic 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 œuvre votre propre membre nécessite macro comme ceci(juste au cas où vous ne vous voulez 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
3
répondu Paul Fultz II 2014-09-30 18:35:06