C SFINAE inkl std enableif Detlef Wilkening http
C++ SFINAE inkl. std: : enable_if Detlef Wilkening http: //www. wilkening-online. de 08. 01. 2015 http: //www. wilkening-online. de 1 / 44
C++ SFINAE § SFINAE - "Substitution failure is not an error" • Acronym wurde 2002 von David Vandevoorde eingeführt § Im Standard gibt es den Begriff nicht • Der Standard beschreibt in § 14. 8. 2 den Sachverhalt • Ohne aber ein explizites Acronym zu verwenden http: //www. wilkening-online. de 2 / 44
C++ SFINAE § Worum geht es eigentlich? § Situation • Eine Menge von überladenenen Funktionen • Alle sind Kandidaten für einen potentiellen Funktions-Aufruf • Mindestens eine dieser Funktion ist ein Funktions-Template • Die Template-Argumente werden deduziert • Hierbei ergibt sich ein auf dem deduzierten Template-Typ beruhender Fehler in der Funktions-Schnittstelle • Ein "Failure" beruhend auf der "Substitution" § => § Dies ist dann kein Compiler-Fehler • Substitution Failure is not an Error § Sondern das Funktions-Template wird einfach aus der Menge der Kandidaten entfernt • Der Compile-Vorgang läuft einfach weiter http: //www. wilkening-online. de 3 / 44
C++ SFINAE § Das ist alles - " substitution failure is not an error" § Fertig http: //www. wilkening-online. de 4 / 44
C++ SFINAE § Das ist alles - " substitution failure is not an error" § Fertig § Okay, ein paar Beispiele und Anwendungen sind wohl noch ganz hilfreich… http: //www. wilkening-online. de 5 / 44
C++ SFINAE § Also eine Menge von überladenen Funktionen • Alle sind Kandidaten für einen potentiellen Funktions-Aufruf § Mindestens eine dieser Funktion ist ein Funktions-Template void print(long l) { cout << "l: " << l << endl; } template<class T> void print(T t) { cout << "T: " << t << endl; } template<class T> void print(T* t) { cout << "T*: " << *t << endl; } http: //www. wilkening-online. de int n = 42; print(n); // => T: 42 print(&n); // => T*: 42 print(43 L); // => l: 43 6 / 44
C++ SFINAE § Die Template-Argumente werden deduziert § Hierbei ergibt sich ein auf dem deduzierten Template-Typ beruhender Fehler in der Funktions-Schnittstelle • Ein "Failure" beruhend auf der "Substitution" § => Dies ist dann kein Compiler-Fehler • Substitution Failure is not an Error • Das Funktions-Template wird einfach aus der Menge der Kandidaten entfernt • SFINAE schlägt während der Funktions-Überladen Auflösung zu // Zusaetzlich template<class T> void print(typename T: : type t) { cout << "T: : type: " << t << endl; } // => Kein Problem http: //www. wilkening-online. de int n = 42; print(n); // => T: 42 print(&n); // => T*: 42 print(43 L); // => l: 43 7 / 44
C++ SFINAE § SFINAE wurde genau dafür eingeführt § Bestehender Code sollte nicht ungültig werden, wenn zusätzlich (z. B. durch einen erweiterten Header) ein Funktions-Template in die Menge der potentiellen Aufruf-Kandidaten hinzukommt, das nach der Typ-Deduktion nicht "okay" ist. // Zusaetzlich template<class T> void print(typename T: : type t) { cout << "T: : type: " << t << endl; } // => Kein Problem http: //www. wilkening-online. de 8 / 44
C++ SFINAE Ganz nebenbei: § Wie spricht man unser neues Funktions-Template an? http: //www. wilkening-online. de 9 / 44
C++ SFINAE void print(long l) { cout << "l: " << l << endl; } template<class T> void print(T t) { cout << "T: " << t << endl; } template<class T> void print(T* t) { cout << "T*: " << *t << endl; } template<class T> void print(typename T: : type t) { cout << "T: : type: " << t << endl; } http: //www. wilkening-online. de 10 / 44
C++ SFINAE struct A { typedef bool type; }; int main() { cout << boolalpha; int n = 42; print(n); // => T: 42 print(&n); // => T*: 42 print(43 L); // => long: 43 print(false); // => T: false print<A>(true); // => T: : type: true } http: //www. wilkening-online. de 11 / 44
C++ SFINAE § SFINAE bezieht sich nicht nur auf Failures in den Parametern • Sondern auch auf • • • Alle Typen im Funktions-Typ (Parameter, Rückgabe, …) Alle Typen in der Template-Parameter Deklaration Seit C++11 auch auf alle Ausdrücke in den Template- und Funktions-Typen - Achtung - wird von MSVS noch nicht unterstützt § Aber nicht in der Implementierung! // Zusaetzlich auch kein Problem // - Return-Typ wirft Funktions-Template raus template<class T> typename T: : type print(T t) { cout << "T=>: " << t << endl; } http: //www. wilkening-online. de 12 / 44
C++ SFINAE § Nicht in der Implementierung • Substitution-Failure dort sind kein SFINAE // Aenderung am bisherigem Funktions-Template in der Implementierung template<class T> void print(T t) { typename T: : type x; // <= kein SFINAE => Compiler-Fehler cout << "T: " << t << endl; } http: //www. wilkening-online. de 13 / 44
C++ SFINAE § Weitere SFINAE Fehler • Array von void, Referenzen, Größe "0", usw. zu erzeugen • Typ ungleich Enums und Klasse links von : : • Nutzung eines Members, den es nicht gibt bzw. der sich in Typ oder Template • • • -Parametern unterscheidet Zeiger auf Referenz auf void Zeiger auf Member von T, wenn T keine Klasse ist Ungültiger Typ für einen Non-Type Template-Parameter Unerlaubte Konvertierungen in Template-Ausdrücken oder Ausdrücken in Funktions-Deklarationen Funktions-Typ mit Rückgabe von Arrays Funktions-Typ mit cv-Qualifier (C++11) Funktions-Typ mit Rückgabe abstrakte Klasse (C++11) Instanziierung von Template-Parameter Packs mit unterschiedlicher Länge (C+11) http: //www. wilkening-online. de 14 / 44
C++ SFINAE § Beispiel für Array der Größe 0 • Beispiel von cppreference • http: //en. cppreference. com/w/cpp/language/sfinae // Diese Funktion wird genommen, wenn I gerade ist template<int I> void div(char(*)[I % 2 == 0] = 0) { } // Diese Funktion wird genommen, wenn I ungerade ist template<int I> void div(char(*)[I % 2 == 1] = 0) { } http: //www. wilkening-online. de 15 / 44
C++ SFINAE § Kann man denn auch was Sinnvolles mit SFINAE machen? § Oder ist es nur ein Sprach-Feature, damit bestehender Code nicht bricht? • Letzlich der Sinn hinter § 14. 8. 2 http: //www. wilkening-online. de 16 / 44
C++ SFINAE § Es wird interessant, wenn man Funktions-Templates mit einer allgemeinen Funktion mit einem Ellipsis Parameter kombiniert • Denn: • • • Der Ellipsis Parameter hat die niedrigste Stufe in der Überladen-Hierarchie => Kommt also nur zum Tragen, wenn nichts anderes greift Aber er greift bei jedem Argument • Aber Beispiel (nächste Folie) sieht (noch) langweilig aus http: //www. wilkening-online. de 17 / 44
C++ SFINAE void fct(. . . ) { cout << "Ellipsis" << endl; } template<class T> void fct(typename T: : type t) { cout << "T: : type " << t << endl; } struct A { typedef int type; }; fct(42); // => Ellipsis fct<A>(42); // => T: : type 42 http: //www. wilkening-online. de 18 / 44
C++ SFINAE § Wenn man jetzt auch noch die Ellipsis-Funktion zu einem Funktions-Template macht. . . • Damit man auch sie mit einer expliziten Typ-Deduktion aufrufen kann template<class> void fct(. . . ) { cout << "Ellipsis" << endl; } template<class T> void fct(typename T: : type t) { cout << "T: : type " << t << endl; } struct A { typedef int type; }; fct<int>(42); // => Ellipsis fct<A>(42); // => T: : type 42 http: //www. wilkening-online. de 19 / 44
C++ SFINAE § Dann kann man jetzt z. B. einen Member-Checker bauen • Einen Compile-Time Member-Checker • Daher zur Compile-Zeit checken, ob ein Member vorhanden ist • Und abhängig davon eine Compile-Zeit Konstante setzen § Oder auch Checks für viele andere Dinge § Einfaches Beispiel: • Hat ein Typ eine "Init" Funktion? • Abhängig vom Ergebnis könnte man dann die Funktion aufrufen oder nicht • Außerdem kann die Lösung so nicht mit Vererbung umgehen • Und ist auch nicht gut parameterisierbar • Ist halt nur einfaches Beispiel http: //www. wilkening-online. de 20 / 44
C++ SFINAE template<typename T> struct Has. Init. Fct { typedef char Yes[1]; typedef char No[2]; template<typename U, U> struct Signature. Check; template<typename V> static Yes& check(Signature. Check<void(T: : *)(), &V: : init>*); template<typename> No& static check(. . . ); static bool const result = (sizeof(check<T>(0)) == 1); }; http: //www. wilkening-online. de 21 / 44
C++ SFINAE § Hinweise • Das Ergebnis findet sich in der Variable „result“ • Für die Unterscheidung werden die Aufrufe der überladenen Funktion • „check“ genommen Damit klar ist, welche Funktion genommen wird, wird die Rückgabe eindeutig unterscheidbar gemacht. • • Yes ist genau 1 Byte groß No ist genau 2 Byte groß • Da Funktionen keine Arrays zurückgeben können, werden Referenzen • • zurückgegeben Die Ellipse paßt auf jeden Zeiger, aber der konkrete Zeiger in der Yes-Check Funktion paßt prinzipiell besser Hat der Typ T keine passende Funktion, so ist die Yes-Funktions-Signatur ungültig und die Funktion wird ausgeschlossen (SFINAE) http: //www. wilkening-online. de 22 / 44
C++ SFINAE struct No. Init { }; struct With. Init { void init() {} }; cout << boolalpha; cout << "No. Init -> " << Has. Init. Fct<No. Init>: : result; cout << "With. Init -> " << Has. Init. Fct<With. Init>: : result; http: //www. wilkening-online. de // => false // => true 23 / 44
C++ SFINAE § Und wie ruft man jetzt "init()" auf bzw. nicht? § Dafür benötigt man etwas TMP • Aufrufe z. B. in Klassen legen • Statische Funktion • TMP If • In Abhängigkeit von Has. Init. Fct<>: : result den Typ wählen • Aufruft der Funktion über den Typ http: //www. wilkening-online. de 24 / 44
C++ SFINAE struct Init. No { template<class T> static void init(T&) { } }; struct Init. Yes { template<class T> static void init(T& t) { t. init(); } }; http: //www. wilkening-online. de 25 / 44
C++ SFINAE template<bool Expr, class True. Type, class False. Type> struct Type. If { typedef True. Type; }; template<class True. Type, class False. Type> struct Type. If<false, True. Type, False. Type> { typedef False. Type; }; template<class T> void init (T& t) { typedef typename Type. If<Has. Init. Fct<T>: : result, Init. Yes, Init. No>: : Type; Type: : init(t); } http: //www. wilkening-online. de 26 / 44
C++ SFINAE § Und was ist das Problem mit der Vererbung? http: //www. wilkening-online. de 27 / 44
C++ SFINAE struct No. Init { }; struct With. Init { void init() {} }; struct Inherited. Init : With. Init { }; struct Double. Inherited. Init : Inherited. Init { }; http: //www. wilkening-online. de 28 / 44
C++ SFINAE cout cout << << << boolalpha; "No. Init -> "With. Init -> " Inh. Init -> "DInh. Init -> " " << << Has. Init. Fct<No. Init>: : result; Has. Init. Fct<With. Init>: : result; Has. Init. Fct<Inhe. . . Init>: : result; Has. Init. Fct<Doubl. . . Init>: : result; => No. Init With. Init Inh. Init DInh. Init -> -> false true false http: //www. wilkening-online. de 29 / 44
C++ SFINAE § Lösung • Eigene lokale Klasse, die von T und einer lokalen Mixin-Klasse erbt • Die Mixin-Klasse hat die Funktion • => Die lokale Mixed Klasse erbt die Funktion von der Mixin-Klasse • Hat aber T die Funktion auch (direkt oder geerbt), • dann wird die Funktion zweimal geerbt => Ohne klare Entscheidung für eine der geerbten Funktionen ist das fehlerhaft => SFINAE => Funktion wird ausgeschlossen => Ellipsen-Check Funktion gewinnt § Achtung • Yes und No sind hier zwischen den Check-Funktionen getauscht worden http: //www. wilkening-online. de 30 / 44
C++ SFINAE template<typename T> struct Has. Real. Init. Fct { typedef char Yes[1]; typedef char No[2]; struct Mixin { void init(){} }; struct Mixed : public T, public Mixin {}; template<typename U, U> struct Sig. Check; template<typename V> static No& check(Sig. Check<void(Mixin: : *)(), &V: : init>*); template<typename> static Yes& check(. . . ); static bool const result = (sizeof(check<Mixed>(0))==1); }; http: //www. wilkening-online. de 31 / 44
C++ SFINAE cout cout << << << boolalpha; "No. Init -> "With. Init -> " Inh. Init -> "DInh. Init -> " " << << Has. Real. Init. Fct<No. Init>: : result; Has. Real. Init. Fct<With. Init>: : result; Has. Real. Init. Fct<I. . . Init>: : result; Has. Real. Init. Fct<D. . . Init>: : result; => No. Init With. Init Inh. Init DInh. Init -> -> false true http: //www. wilkening-online. de 32 / 44
C++ SFINAE Achtung, nochmal der Hinweis § SFINAE ist ein Sprachmittel § Kein Idiom § Auch wenn es manchmal als solches bezeichnet wird • Ist z. B. Teil des Wikibooks "More C++ Idioms" • http: //en. wikibooks. org/wiki/More_C%2 B%2 B_Idioms § Aber es gibt Idioms, die auf SFINAE aufsetzen • Z. B. "Member Detector" • • http: //en. wikibooks. org/wiki/More_C%2 B%2 B_Idioms/Member_Detector Ein einfaches Beispiel dafür hatten wir gerade • Siehe nächste Folien • Oder Enable-If http: //www. wilkening-online. de 33 / 44
C++ SFINAE § Die vielleicht wichtigste Anwendung von SFINAE ist "enable_if" • Vorhanden in Boost, aber seit C++11 auch im Standard • Enable-If erlaubt Funktionen zu überladen, die in Abhängigkeit von einer user • -definierten Compile-Zeit Bedingung aufgerufen werden Header <type_traits> § Beispiele • 1) Nutzung von SFINAE über den Rückgabe-Typ • 2) Wie 1 nur ohne void • 3) Wie 2 mit C++14 • 4) Nutzung von SFINAE über einen Extra-Parameter http: //www. wilkening-online. de 34 / 44
C++ SFINAE template<class T> typename enable_if<is_pod<T>: : value, void>: : type reset(T& t) { cout << "PODn"; memset(&t, 0, sizeof(T)); } template<class T> typename enable_if<!is_pod<T>: : value, void>: : type reset(T& t) { cout << "Kein PODn"; t. reset(); } http: //www. wilkening-online. de 35 / 44
C++ SFINAE struct A { A() {} void reset() { s=""; } string s; }; int main() { int n; reset(n); // => POD A a; reset(a); // => Kein POD } http: //www. wilkening-online. de 36 / 44
C++ SFINAE § Man kann das "void" sogar weglassen § => Default Template-Argument template<class T> typename enable_if<is_pod<T>: : value>: : type reset(T& t) { cout << "PODn"; memset(&t, 0, sizeof(T)); } template<class T> typename enable_if<!is_pod<T>: : value>: : type reset(T& t) { cout << "Kein PODn"; t. reset(); } http: //www. wilkening-online. de 37 / 44
C++ SFINAE § In C++14 gibt es zusätzlich "enable_if_t" • Resultat-Typ "type" muß nicht ausgwiesen werden • using enable_if_t = typename enable_if<B, T>: : type; • Warum gibt es eigentlich kein "is_pod_v"? template<class T> typename enable_if_t<is_pod<T>: : value> reset(T& t) { cout << "PODn"; memset(&t, 0, sizeof(T)); } template<class T> typename enable_if_t<!is_pod<T>: : value> reset(T& t) { cout << "Kein PODn"; t. reset(); } http: //www. wilkening-online. de 38 / 44
C++ SFINAE § Man kann natürlich auch einen Parameter für SFINAE nutzen § Da das T selber nicht geht, z. B. ein extra T* mit Default-Argument template<class T> void reset(T& t, typename enable_if<is_pod<T>: : value, T>: : type* = nullptr) { cout << "PODn"; memset(&t, 0, sizeof(T)); } template<class T> void reset(T& t, typename enable_if<!is_pod<T>: : value, T>: : type* = nullptr) { cout << "Kein PODn"; t. reset(); } http: //www. wilkening-online. de 39 / 44
C++ SFINAE § Mögliche Implementierung von "enable_if" • Von cppreference. com • http: //en. cppreference. com/w/cpp/types/enable_if template<bool B, class T = void> struct enable_if { typedef T type; }; template<class T> struct enable_if<false, T> { }; http: //www. wilkening-online. de 40 / 44
C++ SFINAE § SFINAE in Kombination mit enable_if und den Type-Traits von C++ liefert viele mächtige Compile-Zeit Entscheidungen • Siehe Header Type-Traits bzw. § 20. 10 • Beispiele: § is_void § is_reference § is_standard_layout § is_null_pointer § is_arithmetic § is_pod § is_integral § is_fundamental § is_literal_type § is_floating_point § is_object § is_empty § is_array § is_scalar § is_polymorphic § is_pointer § is_compound § is_abstract § is_lvalue_reference § is_member_pointer § is_final § is_rvalue_reference § is_const § is_trivially_assignable § is_member_object_pointer § is_volatile § is_trivially_copy_assignable § is_member_function_pointer § is_trivially_move_assignable § is_enum § is_trivially_copyable § is_trivially_destructible § is_union § … § is_class § is_function § … http: //www. wilkening-online. de 41 / 44
C++ SFINAE § Bücher • Nicolai M. Josuttis & David Vandevoorde • • • C++ Templates The Complete Guide Addison-Wesley Longman ISBN: 978 -0201734843 1. Auflage, November 2002 Neue Auflage für C++14 in Vorbereitung - 2. Auflage, Juli 2015 ISBN: 978 -0321714121 • Davide Di Gennaro • • Advanced C++ Metaprogramming Create. Space Independent Publishing Platform ISBN: 978 -1460966167 1. Auflage, Juni 2011 http: //www. wilkening-online. de 42 / 44
C++ SFINAE § Links • http: //en. wikipedia. org/wiki/Substitution_failure_is_not_an_error • http: //en. wikibooks. org/wiki/More_C%2 B%2 B_Idioms/SFINAE • http: //en. cppreference. com/w/cpp/language/sfinae • http: //en. cppreference. com/w/cpp/types/enable_if • http: //eli. thegreenplace. net/2014/sfinae-and-enable_if/ • http: //nonchalantlytyped. net/blog/2012/06/27/yet-another-sfinae/ • http: //blog. olivierlanglois. net/index. php/2007/09/01/what_is_the_c_sfinae_ • • • principle http: //blog. cplus-soup. com/2006/09/learning-about-sfinae. html http: //www. ddj. com/cpp/184401659 http: //www. semantics. org/once_weakly/w 02_SFINAE. pdf http: //people. mpiinf. mpg. de/~kettner/courses/lib_design_03/notes/meta. html#Constraining http: //flamingdangerzone. com/cxx 11/2013/02/11/to-sfinae-or-not-tosfinae. html http: //www. wilkening-online. de 43 / 44
C++ SFINAE Fragen? http: //www. wilkening-online. de 44 / 44
- Slides: 44