Esempi di CINA-SFINAE?

Voglio entrare in più meta-programmazione template. So che SFINAE sta per “il fallimento della sostituzione non è un errore”. Ma qualcuno può mostrarmi un buon uso per SFINAE?

Ecco un esempio ( da qui ):

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

Quando viene valutato IsClassT::Yes , 0 non può essere convertito in int int::* perché int non è una class, quindi non può avere un puntatore membro. Se SFINAE non esistesse, si otterrebbe un errore del compilatore, qualcosa come “0 non può essere convertito nel puntatore membro per il tipo non di class int”. Invece, usa solo la forma ... che restituisce Due e quindi restituisce false, int non è un tipo di class.

Mi piace usare SFINAE per verificare le condizioni booleane.

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

Può essere abbastanza utile. Ad esempio, l’ho usato per verificare se un elenco di inizializzazione raccolto utilizzando l’operatore virgola non è più lungo di una dimensione fissa

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

L'elenco è accettato solo quando M è minore di N, il che significa che l'elenco di inizializzazione non ha troppi elementi.

La syntax char(*)[C] significa: puntatore a un array con tipo di elemento char e dimensione C Se C è falso (0 qui), allora otteniamo il tipo non valido char(*)[0] , puntatore a un array di dimensioni zero: SFINAE fa in modo che il modello venga ignorato in quel momento.

Espresso con boost::enable_if , che assomiglia a questo

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

In pratica, trovo spesso la capacità di verificare le condizioni di un'abilità utile.

La libreria enable_if di Boost offre una bella interfaccia pulita per l’utilizzo di SFINAE. Uno dei miei esempi di utilizzo preferiti è nella libreria Boost.Iterator . SFINAE viene utilizzato per abilitare le conversioni di tipo iteratore.

In C ++ 11 i test SFINAE sono diventati molto più belli. Ecco alcuni esempi di usi comuni:

Scegli un sovraccarico di funzione in base ai tratti

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

Usando un cosiddetto idioma del sink di tipo si possono fare test abbastanza arbitrari su un tipo come controllare se ha un membro e se quel membro è di un certo tipo

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

Ecco un esempio dal vivo: http://ideone.com/dHhyHE Ho anche recentemente scritto un'intera sezione su SFINAE e tagging dispatch nel mio blog (plug spudorato ma rilevante) http://metaporky.blogspot.de/2014/08/ part-7-static-dispatch-function.html

Nota come in C ++ 14 c'è uno std :: void_t che è essenzialmente lo stesso del mio TypeSink qui.

Ecco un altro (in ritardo) esempio di SFINAE , basato sulla risposta di 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); 

In questo modo, puoi controllare il value del valore per vedere se T è una class o no:

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

C ++ 17 probabilmente fornirà un mezzo generico per interrogare le funzionalità. Vedere N4502 per i dettagli, ma come esempio indipendente considerare quanto segue.

Questa parte è la parte costante, mettila in un colpo di testa.

 // 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’esempio seguente, tratto da N4502 , mostra l’utilizzo:

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

Rispetto alle altre implementazioni, questo è abbastanza semplice: un insieme ridotto di strumenti ( void_t e detect ) è sufficiente. Inoltre, è stato segnalato (vedi N4502 ) che è misurabilmente più efficiente (consumo della memoria del compilatore e del compilatore) rispetto ai precedenti approcci.

Ecco un esempio dal vivo , che include modifiche alla portabilità per GCC pre 5.1.

Ecco un buon articolo di SFINAE: Un’introduzione al concetto SFINAE di C ++: introspezione in fase di compilazione di un membro della class .

Sommario come segue:

 /* 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  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::string>::type serialize(const T& obj) { return obj.serialize(); } template  typename enable_if::value, std::string>::type serialize(const T& obj) { return to_string(obj); } 

declval è un’utilità che fornisce un “riferimento falso” a un object di un tipo che non può essere facilmente costruito. declval è veramente utile per le nostre costruzioni 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'; }