web-dev-qa-db-fra.com

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 une bonne utilisation de SFINAE?

106
rlbond

Voici un exemple ( d'ici ):

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

Quand IsClassT<int>::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, vous obtiendriez une erreur de compilation, quelque chose comme "0 ne peut pas être converti en pointeur de membre pour le type non-classe int". Au lieu de cela, il utilise simplement le ... forme qui renvoie Two, et est donc évaluée à false, int n'est pas un type de classe.

63
Greg Rogers

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

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

template<int I> 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'initialiseurs collectée à l'aide d'une virgule opérateur n'est pas plus longue qu'une taille fixe

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

La liste n'est acceptée que lorsque M est plus petit que N, ce qui signifie que la liste d'initialisation n'a pas trop d'éléments.

La syntaxe char(*)[C] signifie: pointeur vers 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 nulle: SFINAE fait en sorte que le modèle soit alors ignoré.

Exprimé avec boost::enable_if, Qui ressemble à ceci

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

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

86

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

Choisissez une surcharge de fonction en fonction des traits

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

En utilisant un idiome dit de récepteur de type, vous pouvez faire 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<typename T>
struct TypeSink{
    using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;

//use case
template<typename T, typename=void>
struct HasBarOfTypeInt : std::false_type{};
template<typename T>
struct HasBarOfTypeInt<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>> :
    std::is_same<typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type,int>{};


struct S{
   int bar;
};
struct K{

};

template<typename T, typename = TypeSinkT<decltype(&T::bar)>>
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<S>::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 répartition des tags dans mon blog (plug sans vergogne mais pertinent) http://metaporky.blogspot.de/2014/08/part-7-static-dispatch-function.html

Notez qu'à partir de C++ 14, il y a un std :: void_t qui est essentiellement le même que mon TypeSink ici.

12
odinthenerd

La bibliothèque enable_if de Boost offre une belle interface propre pour utiliser SFINAE. 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 itérateur.

9
David Joyner

C++ 17 fournira probablement un moyen générique pour rechercher des fonctionnalités. Voir N4502 pour plus de détails, mais comme exemple autonome, considérez ce qui suit.

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

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

L'exemple suivant, tiré de N4502 , montre l'utilisation:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

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

Voici un exemple en direct , qui inclut des ajustements de portabilité pour GCC pre 5.1.

3
akim

Voici un autre exemple (tardif) SFINAE , basé sur Greg Rogersréponse :

template<typename T>
class IsClassT {
    template<typename C> static bool test(int C::*) {return true;}
    template<typename C> static bool test(...) {return false;}
public:
    static bool value;
};

template<typename T>
bool IsClassT<T>::value=IsClassT<T>::test<T>(0);

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

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

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

Résumez-le comme suit:

/*
 The compiler 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 compiler doesn't throw a bunch of errors.
 It simply tries the next overload. 
*/
template <typename T> void f(const T& t, typename T::iterator* it = nullptr) { }

// The sink-hole.
void f(...) { }

f(1); // Calls void f(...) { }

template<bool B, class T = void> // Default template version.
struct enable_if {}; // This struct doesn't define "type" and the substitution will fail if you try to access it.

template<class T> // A specialisation used if the expression is true. 
struct enable_if<true, T> { typedef T type; }; // This struct do have a "type" and won't fail on access.

template <class T> typename enable_if<hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return obj.serialize();
}

template <class T> typename enable_if<!hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return to_string(obj);
}

declval est un utilitaire qui vous donne une "fausse référence" à un objet d'un type qui ne peut pas être facilement construit. declval est vraiment 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<NonDefault>().foo()) n2 = n1; // int n2
    std::cout << "n2 = " << n2 << '\n';
}
1
zangw

Un nouveau blog existe depuis la dernière réponse à ce fil.

C'est Fluent C++: http://fluentcpp.com/

Il existe de nombreux exemples pour une recherche sur "SFINAE".

0
Sandburg