Exemples C ++ SFINAE?

Je veux entrer dans plus de méta-programmation de modèles. Je sais que SFINAE signifie “l’échec de la substitution n’est pas une erreur”. Mais quelqu’un peut-il me montrer un bon usage pour SFINAE?

    Voici un exemple:

    template class IsClassT { private: typedef char One; typedef struct { char a[2]; } Two; template static One test(int C::*); // Will be chosen if T is anything except a class. template static Two test(...); public: enum { Yes = sizeof(IsClassT::test(0)) == 1 }; enum { No = !Yes }; }; 

    Lorsque IsClassT::Yes est évalué, 0 ne peut pas être converti en int int::* car int n’est pas une classe, il ne peut donc pas avoir de pointeur membre. Si SFINAE n’existait pas, alors vous obtiendriez une erreur de compilation, quelque chose comme ‘0 ne peut pas être converti en pointeur de membre pour un type non-classe int’. Au lieu de cela, il utilise simplement la forme ... qui retourne Two, et évalue donc false, int n’est pas un type de classe.

    J’aime utiliser SFINAE pour vérifier les conditions booléennes.

     template void div(char(*)[I % 2 == 0] = 0) { /* this is taken when I is even */ } template void div(char(*)[I % 2 == 1] = 0) { /* this is taken when I is odd */ } 

    Cela peut être très utile. Par exemple, je l’ai utilisé pour vérifier si une liste d’initialisation collectée à l’aide de la virgule de l’opérateur ne dépasse pas une taille fixe.

     template struct Vector { template Vector(MyInitList const& i, char(*)[M < = N] = 0) { /* ... */ } } 

    La liste n'est acceptée que lorsque M est inférieur à N, ce qui signifie que la liste d'initialisation ne contient pas trop d'éléments.

    La syntaxe char(*)[C] signifie: Pointeur sur un tableau avec le type d’élément char et la taille C Si C est faux (0 ici), alors nous obtenons le type non valide char(*)[0] , pointeur vers un tableau de taille zéro: SFINAE fait en sorte que le modèle sera ignoré à ce moment-là.

    Exprimé avec boost::enable_if , ça ressemble à ça

     template struct Vector { template Vector(MyInitList const& i, typename enable_if_c< (M <= N)>::type* = 0) { /* ... */ } } 

    En pratique, je trouve souvent que la possibilité de vérifier les conditions est une capacité utile.

    La bibliothèque enable_if de Boost offre une interface propre à l’utilisation de SFINAE. L’un de mes exemples d’utilisation préférés se trouve dans la bibliothèque Boost.Iterator . SFINAE est utilisé pour activer les conversions de type iterator.

    En C ++ 11, les tests SFINAE sont devenus beaucoup plus jolis. Voici quelques exemples d’utilisations courantes:

    Choisissez une fonction en surcharge en fonction des traits

     template std::enable_if_t::value> f(T t){ //integral version } template std::enable_if_t::value> f(T t){ //floating point version } 

    En utilisant un idiome de type puits, vous pouvez effectuer des tests assez arbitraires sur un type comme vérifier s’il a un membre et si ce membre est d’un certain type

     //this goes in some header so you can use it everywhere template struct TypeSink{ using Type = void; }; template using TypeSinkT = typename TypeSink::Type; //use case template struct HasBarOfTypeInt : std::false_type{}; template struct HasBarOfTypeInt().*(&T::bar))>> : std::is_same().*(&T::bar))>::type,int>{}; struct S{ int bar; }; struct K{ }; template> void print(T){ std::cout < < "has bar" << std::endl; } void print(...){ std::cout << "no bar" << std::endl; } int main(){ print(S{}); print(K{}); std::cout << "bar is int: " << HasBarOfTypeInt::value < < std::endl; } 

    Voici un exemple en direct: http://ideone.com/dHhyHE J'ai aussi récemment écrit une section entière sur SFINAE et la dissortingbution de tags sur mon blog (fiche sans vergogne mais pertinente) http://metaporky.blogspot.de/2014/08/ part-7-static-dispatch-function.html

    Notez qu'à partir de C ++ 14, il existe un std :: void_t qui est essentiellement identique à mon TypeSink ici.

    Voici un autre exemple (tardif) de SFINAE , basé sur la réponse de Greg Rogers :

     template class IsClassT { template static bool test(int C::*) {return true;} template static bool test(...) {return false;} public: static bool value; }; template bool IsClassT::value=IsClassT::test(0); 

    De cette façon, vous pouvez vérifier la value la valeur pour voir si T est une classe ou non:

     int main(void) { std::cout < < IsClassT::value < < std::endl; // true std::cout << IsClassT::value < < std::endl; // false return 0; } 

    C ++ 17 fournira probablement un moyen générique d’interroger les fonctionnalités. Voir N4502 pour plus de détails, mais en guise d’exemple, prenez en compte les éléments suivants.

    Cette partie est la partie constante, placez-la dans un en-tête.

     // See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf. template  using void_t = void; // Primary template handles all types not supporting the operation. template  class, typename = void_t<>> struct detect : std::false_type {}; // Specialization recognizes/validates only types supporting the archetype. template  class Op> struct detect>> : std::true_type {}; 

    L’exemple suivant, extrait de N4502 , montre l’utilisation:

     // Archetypal expression for assignment operation. template  using assign_t = decltype(std::declval() = std::declval()) // Trait corresponding to that archetype. template  using is_assignable = detect; 

    Par rapport aux autres implémentations, celle-ci est assez simple: un ensemble réduit d’outils ( void_t et void_t ) suffit. En outre, il a été rapporté (voir N4502 ) qu’il est nettement plus efficace (à la compilation et à la mémoire du compilateur) que les approches précédentes.

    Voici un exemple en direct , qui comprend des réglages de portabilité pour GCC pre 5.1.

    Voici un bon article de SFINAE: Une introduction au concept SFINAE de C ++: une introspection à la compilation d’un membre de la classe .

    Résumé comme suit:

     /* The comstackr will try this overload since it's less generic than the variadic. T will be replace by int which gives us void f(const int& t, int::iterator* b = nullptr); int doesn't have an iterator sub-type, but the comstackr doesn't throw a bunch of errors. It simply sortinges the next overload. */ template  void f(const T& t, typename T::iterator* it = nullptr) { } // The sink-hole. void f(...) { } f(1); // Calls void f(...) { } 

     template // Default template version. struct enable_if {}; // This struct doesn't define "type" and the substitution will fail if you try to access it. template // A specialisation used if the expression is true. struct enable_if { typedef T type; }; // This struct do have a "type" and won't fail on access. template  typename enable_if::value, std::ssortingng>::type serialize(const T& obj) { return obj.serialize(); } template  typename enable_if< !hasSerialize::value, std::ssortingng>::type serialize(const T& obj) { return to_ssortingng(obj); } 

    declval est un utilitaire qui vous donne une “fausse référence” à un object d’un type qui ne pourrait pas être facilement construit. declval est très pratique pour nos constructions SFINAE.

     struct Default { int foo() const {return 1;} }; struct NonDefault { NonDefault(const NonDefault&) {} int foo() const {return 1;} }; int main() { decltype(Default().foo()) n1 = 1; // int n1 // decltype(NonDefault().foo()) n2 = n1; // error: no default constructor decltype(std::declval().foo()) n2 = n1; // int n2 std::cout < < "n2 = " << n2 << '\n'; }