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".

3voto

whoan Points 6149

En voici un autre (en retard) SFINAE par exemple, sur la base de Greg Rogers 's ré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 le 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;
}

3voto

zangw Points 401

Voici un bon article de SFINAE : Introduction au concept SFINAE du C++ : introspection en temps réel d'un membre de classe. .

Le résumé est le suivant :

/*
 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 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<NonDefault>().foo()) n2 = n1; // int n2
    std::cout << "n2 = " << n2 << '\n';
}

3voto

cowboy Points 31

Le code suivant utilise SFINAE pour permettre au compilateur de sélectionner une surcharge en fonction du fait qu'un type possède ou non une certaine méthode :

    #include <iostream>

    template<typename T>
    void do_something(const T& value, decltype(value.get_int()) = 0) {
        std::cout << "Int: " <<  value.get_int() << std::endl;
    }

    template<typename T>
    void do_something(const T& value, decltype(value.get_float()) = 0) {
        std::cout << "Float: " << value.get_float() << std::endl;
    }

    struct FloatItem {
        float get_float() const {
            return 1.0f;
        }
    };

    struct IntItem {
        int get_int() const {
            return -1;
        }
    };

    struct UniversalItem : public IntItem, public FloatItem {};

    int main() {
        do_something(FloatItem{});
        do_something(IntItem{});
        // the following fails because template substitution
        // leads to ambiguity 
        // do_something(UniversalItem{});
        return 0;
    }

Sortie :

Float: 1
Int: -1

2voto

Ben_LCDB Points 473

Les exemples fournis par les autres réponses me semblent plus compliqués que nécessaire.

Voici l'exemple un peu plus facile à comprendre de Référence cpp :

#include <iostream>

// this overload is always in the set of overloads
// ellipsis parameter has the lowest ranking for overload resolution
void test(...)
{
    std::cout << "Catch-all overload called\n";
}

// this overload is added to the set of overloads if
// C is a reference-to-class type and F is a pointer to member function of C
template <class C, class F>
auto test(C c, F f) -> decltype((void)(c.*f)(), void())
{
    std::cout << "Reference overload called\n";
}

// this overload is added to the set of overloads if
// C is a pointer-to-class type and F is a pointer to member function of C
template <class C, class F>
auto test(C c, F f) -> decltype((void)((c->*f)()), void())
{
    std::cout << "Pointer overload called\n";
}

struct X { void f() {} };

int main(){
  X x;
  test( x, &X::f);
  test(&x, &X::f);
  test(42, 1337);
}

Sortie :

Reference overload called
Pointer overload called
Catch-all overload called

Comme vous pouvez le voir, lors du troisième appel de test, la substitution échoue sans erreur.

0voto

user Points 1825

Ici, j'utilise la surcharge de fonction template (pas directement SFINAE) pour déterminer si un pointeur est un pointeur de fonction ou de classe membre : ( Est-il possible de corriger les pointeurs des fonctions membres de iostream cout/cerr qui sont imprimés comme 1 ou vrai ? )

https://godbolt.org/z/c2NmzR

#include<iostream>

template<typename Return, typename... Args>
constexpr bool is_function_pointer(Return(*pointer)(Args...)) {
    return true;
}

template<typename Return, typename ClassType, typename... Args>
constexpr bool is_function_pointer(Return(ClassType::*pointer)(Args...)) {
    return true;
}

template<typename... Args>
constexpr bool is_function_pointer(Args...) {
    return false;
}

struct test_debugger { void var() {} };
void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}

int main(void) {
    int* var;

    std::cout << std::boolalpha;
    std::cout << "0. " << is_function_pointer(var) << std::endl;
    std::cout << "1. " << is_function_pointer(fun_void_void) << std::endl;
    std::cout << "2. " << is_function_pointer(fun_void_double) << std::endl;
    std::cout << "3. " << is_function_pointer(fun_double_double) << std::endl;
    std::cout << "4. " << is_function_pointer(&test_debugger::var) << std::endl;
    return 0;
}

Imprimés

0. false
1. true
2. true
3. true
4. true

Tel que le code est, il pourrait (selon la "bonne" volonté du compilateur) génère un appel à une fonction au moment de l'exécution qui renverra vrai ou faux. Si vous souhaitez forcer la is_function_pointer(var) à évaluer au moment de la compilation (aucun appel de fonction n'est effectué au moment de l'exécution), vous pouvez utiliser la fonction constexpr variable :

constexpr bool ispointer = is_function_pointer(var);
std::cout << "ispointer " << ispointer << std::endl;

Selon la norme C++, toutes les constexpr sont garanties d'être évaluées au moment de la compilation ( Calcul de la longueur d'une chaîne de caractères C au moment de la compilation. S'agit-il vraiment d'un constexpr ? ).

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