std :: enable_if pour comstackr conditionnellement 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é que cela ne devrait pas être trop difficile de trouver un exemple simple. Je veux utiliser std::enable_if pour choisir entre deux fonctions membres et ne permettre qu’une seule d’entre elles.

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

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

Pourquoi g ++ ne supprime-t-il pas l’instanciation incorrecte pour la deuxième fonction membre? Selon la norme, std::enable_if::type n’existe que lorsque le paramètre du 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 provient du problème que g ++ ne supprime pas la fonction deuxième membre et estime que cela devrait être une surcharge.

SFINAE ne fonctionne que si la substitution dans l’argument de la 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 ! 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 object de type Y entre autres), il instancie toutes ses déclarations de membres (pas nécessairement leurs définitions / corps!). Parmi eux se trouvent également les modèles de membres. Notez que T est alors connu, et !std::is_same< T, int >::value renvoie false. Donc, il va créer une classe Y qui contient

 class Y { 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::type accède à un std::enable_if::type non existant, de sorte que cette déclaration est mal formée. Et donc votre programme est invalide.

Vous devez faire en sorte que le paramètre enable_if des templates de enable_if dépend 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’arguments pour leurs arguments de modèle se produit et SFINAE se produit comme prévu. Voir cette question et la réponse correspondante sur la façon de le faire.

J’ai fait ce court exemple qui fonctionne également.

 #include  #include  class foo; class bar; template struct check { template typename std::enable_if::value, bool>::type test() { return true; } template typename std::enable_if< !std::is_same::value, bool>::type test() { return false; } }; int main() { check check_foo; check check_bar; if (!check_foo.test() && check_bar.test()) 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 là encore je l'ai fait pour que je me trompe 🙂

Vous pouvez le voir en action ici .

Pour les retardataires qui recherchent une solution qui “ne fait que fonctionner”:

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

Comstackr avec:

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

Running donne:

 ./a.out 11 

De cet article:

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

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’inheritance pour accéder à toutes les autres données sous-jacentes, mais cette technique fonctionne.

 template< class T, bool condition> struct FooImpl; template struct FooImpl { T foo() { return 10; } }; template struct FoolImpl { T foo() { return 5; } }; template< class T > class Y : public FooImpl > // whatever your test is goes here. { public: typedef FooImpl > 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 beaucoup de choses différentes pour différentes fonctions membres, vous devrez créer une classe pour chaque fonction et la chaîner dans l’arborescence. Cela est vrai pour l’access aux membres de données communs.

Ex:

 template class Goo; // repeat pattern above. template class Foo : public Goo > { public: typedef Goo > inherited: // etc. etc. }; 

Le booléen doit dépendre du paramètre de modèle en cours de déduction. Donc, un moyen 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::value && EnableBool )>::type > T foo() { return 10; } }; 

Cependant, cela ne fonctionnera pas si vous souhaitez surcharger la fonction membre. Au lieu de cela, il est préférable d’utiliser TICK_MEMBER_REQUIRES partir de la bibliothèque Tick :

 template< class T > class Y { public: TICK_MEMBER_REQUIRES(std::is_same::value) T foo() { return 10; } TICK_MEMBER_REQUIRES(!std::is_same::value) T foo() { return 10; } }; 

Vous pouvez également implémenter votre propre macro membre requirejse (par exemple, si vous ne voulez pas utiliser une autre bibliothèque):

 template 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