140 votes

Exemples de SFINAE C++ ?

Je veux me lancer dans la méta-programmation de modèles. Je sais que SFINAE est l'acronyme de "substitution failure is not an error". Mais quelqu'un peut-il me montrer une bonne utilisation de SFINAE ?

3 votes

C'est une bonne question. Je comprends assez bien SFINAE, mais je ne pense pas avoir jamais eu à l'utiliser (à moins que les bibliothèques le fassent sans que je le sache).

6 votes

La STL l'a formulé de manière légèrement différente dans FAQs ici "L'échec de la substitution n'est pas un éléphant".

93voto

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 */
}

Il peut être très utile. Par exemple, je l'ai utilisé pour vérifier si une liste d'initialisation collectée à l'aide de l'opérateur virgule 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 si M est inférieur à N, ce qui signifie que la liste d'initialisation ne comporte pas trop d'éléments.

La syntaxe char(*)[C] moyens : Pointeur vers un tableau dont le type d'élément est char et la taille. C . Si C est faux (0 ici), alors nous obtenons le type invalide 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 que la capacité de vérifier les conditions est une capacité utile.

2 votes

@Johannes Bizarrement, GCC (4.8) et Clang (3.2) acceptent de déclarer des tableaux de taille 0 (donc le type n'est pas vraiment "invalide"), pourtant cela se comporte correctement sur votre code. Il y a probablement un support spécial pour ce cas dans le cas de SFINAE par rapport aux utilisations "régulières" des types.

0 votes

@akim : si cela est vrai un jour (bizarre ? ! depuis quand ?) alors peut-être M <= N ? 1 : -1 pourrait fonctionner à la place.

1 votes

@v.oddou Essayez juste int foo[0] . Je ne suis pas surpris qu'il soit supporté, car il permet l'astuce très utile du "struct se terminant par un tableau de longueur 0" ( gcc.gnu.org/onlinedocs/gcc/Zero-Length.html ).

75voto

Greg Rogers Points 18119

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

Lorsque IsClassT<int>::Yes est évalué, 0 ne peut pas être converti en int int::* parce que int n'est pas une classe, donc il ne peut pas avoir de pointeur de membre. Si la SFINAE n'existait pas, vous obtiendriez une erreur de compilation, quelque chose comme "0 cannot be converted to member pointer for non-class type int". Au lieu de cela, il utilise simplement le ... qui renvoie Deux, et donc évalue à faux, int n'est pas un type de classe.

8 votes

@rlbond, j'ai répondu à votre question dans les commentaires de cette question ici : stackoverflow.com/questions/822059/ . En bref : si les deux fonctions de test sont candidates et viables, alors "..." a le pire coût de conversion, et ne sera donc jamais utilisé, au profit de l'autre fonction. "..." est l'ellipse, la chose var-arg : int printf(char const*, ...) ;

0 votes

28 votes

La chose la plus étrange ici, IMO, n'est pas le ... mais plutôt le int C::* que je n'avais jamais vu et que j'ai dû aller chercher. J'ai trouvé la réponse à la question de savoir ce que c'est et à quoi ça peut servir ici : stackoverflow.com/questions/670734/

15voto

PorkyBrain Points 1955

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

Choisir 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 ce que l'on appelle un idiome de type sink, 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 concret : http://ideone.com/dHhyHE J'ai également écrit récemment une section entière sur SFINAE et l'envoi de balises sur mon blog (publicité éhontée 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 le même que mon TypeSink ici.

0 votes

Votre premier bloc de code redéfinit le même modèle.

0 votes

Puisqu'il n'y a aucun type pour lequel is_integral et is_floating_point sont tous deux vrais, cela devrait être un soit ou, car SFINAE en supprimera au moins un.

0 votes

Vous redéfinissez le même modèle avec des arguments de modèle par défaut différents. Avez-vous essayé de le compiler ?

10voto

David Joyner Points 4994

Boost's enable_if offre une interface propre et agréable pour utiliser SFINAE. L'un de mes exemples d'utilisation préférés se trouve dans la section Iterator de Boost bibliothèque. SFINAE est utilisé pour permettre les conversions de type d'itérateur.

4voto

akim Points 1404

C++17 fournira probablement un moyen générique d'interroger les fonctionnalités. Voir N4502 pour plus de détails, mais à titre d'exemple, 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 réduit d'outils ( void_t y detect ) suffit. Par ailleurs, il a été rapporté (voir N4502 ) qu'il est mesurablement plus efficace (temps de compilation et consommation de mémoire du compilateur) que les approches précédentes.

Voici un exemple concret qui inclut des ajustements de portabilité pour GCC pre 5.1.

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