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

Die Präsentation wird geladen. Bitte warten

Die Präsentation wird geladen. Bitte warten

C++ SFINAE / 44 C++ SFINAE inkl. std::enable_if Detlef Wilkening 08.01.2015.

Ähnliche Präsentationen


Präsentation zum Thema: "C++ SFINAE / 44 C++ SFINAE inkl. std::enable_if Detlef Wilkening 08.01.2015."—  Präsentation transkript:

1 C++ SFINAE / 44 C++ SFINAE inkl. std::enable_if Detlef Wilkening

2 C++ SFINAE / 44 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 § den Sachverhalt Ohne aber ein explizites Acronym zu verwenden

3 C++ SFINAE / 44  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

4 C++ SFINAE / 44  Das ist alles - " substitution failure is not an error"  Fertig

5 C++ SFINAE / 44  Das ist alles - " substitution failure is not an error"  Fertig  Okay, ein paar Beispiele und Anwendungen sind wohl noch ganz hilfreich…

6 C++ SFINAE / 44  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 void print(T t) { cout << "T: " << t << endl; } template void print(T* t) { cout << "T*: " << *t << endl; } int n = 42; print(n); // => T: 42 print(&n); // => T*: 42 print(43L); // => l: 43

7 C++ SFINAE / 44  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 void print(typename T::type t) { cout << "T::type: " << t << endl; } // => Kein Problem int n = 42; print(n); // => T: 42 print(&n); // => T*: 42 print(43L); // => l: 43

8 C++ SFINAE / 44  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 void print(typename T::type t) { cout << "T::type: " << t << endl; } // => Kein Problem

9 C++ SFINAE / 44 Ganz nebenbei:  Wie spricht man unser neues Funktions-Template an?

10 C++ SFINAE / 44 void print(long l) { cout << "l: " << l << endl; } template void print(T t) { cout << "T: " << t << endl; } template void print(T* t) { cout << "T*: " << *t << endl; } template void print(typename T::type t) { cout << "T::type: " << t << endl; }

11 C++ SFINAE / 44 struct A { typedef bool type; }; int main() { cout << boolalpha; int n = 42; print(n); // => T: 42 print(&n); // => T*: 42 print(43L); // => long: 43 print(false); // => T: false print (true); // => T::type: true }

12 C++ SFINAE / 44  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 typename T::type print(T t) { cout : " << t << endl; }

13 C++ SFINAE / 44  Nicht in der Implementierung Substitution-Failure dort sind kein SFINAE // Aenderung am bisherigem Funktions-Template in der Implementierung template void print(T t) { typename T::type x; // Compiler-Fehler cout << "T: " << t << endl; }

14 C++ SFINAE / 44  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 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)

15 C++ SFINAE / 44  Beispiel für Array der Größe 0 Beispiel von cppreference // Diese Funktion wird genommen, wenn I gerade ist template void div(char(*)[I % 2 == 0] = 0) { } // Diese Funktion wird genommen, wenn I ungerade ist template void div(char(*)[I % 2 == 1] = 0) { }

16 C++ SFINAE / 44  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 §

17 C++ SFINAE / 44  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

18 C++ SFINAE / 44 void fct(...) { cout << "Ellipsis" << endl; } template void fct(typename T::type t) { cout << "T::type " << t << endl; } struct A { typedef int type; }; fct(42); // => Ellipsis fct (42); // => T::type 42

19 C++ SFINAE / 44  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 void fct(...) { cout << "Ellipsis" << endl; } template void fct(typename T::type t) { cout << "T::type " << t << endl; } struct A { typedef int type; }; fct (42); // => Ellipsis fct (42); // => T::type 42

20 C++ SFINAE / 44  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 ein einfaches Beispiel

21 C++ SFINAE / 44 template struct HasInitFct { typedef char Yes[1]; typedef char No[2]; template struct SignatureCheck; template static Yes& check(SignatureCheck *); template static No& check(...); static bool const result = (sizeof(check (0)) == 1); };

22 C++ SFINAE / 44  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)

23 C++ SFINAE / 44 struct NoInit { }; struct WithInit { void init() {} }; cout << boolalpha; cout " ::result; // => false cout " ::result; // => true

24 C++ SFINAE / 44  Und wie ruft man jetzt "init()" auf bzw. nicht?  Dafür benötigt man etwas TMP Aufrufe z.B. in Klassen legen Statische Funktion In Abhängigkeit von HasInitFct<>::result den Typ wählen TMP If Aufruft der Funktion über den Typ

25 C++ SFINAE / 44 struct InitNo { template static void init(T&) { } }; struct InitYes { template static void init(T& t) { t.init(); } };

26 C++ SFINAE / 44 template struct TypeIf { typedef TrueType Type; }; template struct TypeIf { typedef FalseType Type; }; template void init (T& t) { typedef typename TypeIf ::result, InitYes, InitNo>::Type Type; Type::init(t); }

27 C++ SFINAE / 44  Und was ist das Problem mit der Vererbung?

28 C++ SFINAE / 44 struct NoInit { }; struct WithInit { void init() {} }; struct InheritedInit : WithInit { }; struct DoubleInheritedInit : InheritedInit { };

29 C++ SFINAE / 44 cout << boolalpha; cout " ::result; => NoInit -> false WithInit -> true InhInit -> false DInhInit -> false

30 C++ SFINAE / 44  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

31 C++ SFINAE / 44 template struct HasRealInitFct { typedef char Yes[1]; typedef char No[2]; struct Mixin { void init(){} }; struct Mixed : public T, public Mixin {}; template struct SigCheck; template static No& check(SigCheck *); template static Yes& check(...); static bool const result = (sizeof(check (0))==1); };

32 C++ SFINAE / 44 cout << boolalpha; cout " ::result; => NoInit -> false WithInit -> true InhInit -> true DInhInit -> true

33 C++ SFINAE / 44 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"  Aber es gibt Idioms, die auf SFINAE aufsetzen Z.B. "Member Detector" Ein einfaches Beispiel dafür hatten wir gerade Oder Enable-If Siehe nächste Folien

34 C++ SFINAE / 44  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  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

35 C++ SFINAE / 44 template typename enable_if ::value, void>::type reset(T& t) { cout << "POD\n"; memset(&t, 0, sizeof(T)); } template typename enable_if ::value, void>::type reset(T& t) { cout << "Kein POD\n"; t.reset(); }

36 C++ SFINAE / 44 struct A { A() {} void reset() { s=""; } string s; }; int main() { int n; reset(n); // => POD A a; reset(a); // => Kein POD }

37 C++ SFINAE / 44  Man kann das "void" sogar weglassen  => Default Template-Argument template typename enable_if ::value>::type reset(T& t) { cout << "POD\n"; memset(&t, 0, sizeof(T)); } template typename enable_if ::value>::type reset(T& t) { cout << "Kein POD\n"; t.reset(); }

38 C++ SFINAE / 44  In C++14 gibt es zusätzlich "enable_if_t" Resultat-Typ "type" muß nicht ausgwiesen werden using enable_if_t = typename enable_if ::type; Warum gibt es eigentlich kein "is_pod_v"? template typename enable_if_t ::value> reset(T& t) { cout << "POD\n"; memset(&t, 0, sizeof(T)); } template typename enable_if_t ::value> reset(T& t) { cout << "Kein POD\n"; t.reset(); }

39 C++ SFINAE / 44  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 void reset(T& t, typename enable_if ::value, T>::type* = nullptr) { cout << "POD\n"; memset(&t, 0, sizeof(T)); } template void reset(T& t, typename enable_if ::value, T>::type* = nullptr) { cout << "Kein POD\n"; t.reset(); }

40 C++ SFINAE / 44  Mögliche Implementierung von "enable_if" Von cppreference.com template struct enable_if { typedef T type; }; template struct enable_if { };

41 C++ SFINAE / 44  SFINAE in Kombination mit enable_if und den Type-Traits von C++ liefert viele mächtige Compile-Zeit Entscheidungen Siehe Header Type-Traits bzw. § Beispiele:  is_void  is_null_pointer  is_integral  is_floating_point  is_array  is_pointer  is_lvalue_reference  is_rvalue_reference  is_member_object_pointer  is_member_function_pointer  is_enum  is_union  is_class  is_function  …  is_standard_layout  is_pod  is_literal_type  is_empty  is_polymorphic  is_abstract  is_final  is_trivially_assignable  is_trivially_copy_assignable  is_trivially_move_assignable  is_trivially_destructible  …  is_reference  is_arithmetic  is_fundamental  is_object  is_scalar  is_compound  is_member_pointer  is_const  is_volatile  is_trivial  is_trivially_copyable  …

42 C++ SFINAE / 44  Bücher Nicolai M. Josuttis & David Vandevoorde C++ Templates The Complete Guide Addison-Wesley Longman ISBN: Auflage, November 2002 Neue Auflage für C++14 in Vorbereitung  2. Auflage, Juli 2015  ISBN: Davide Di Gennaro Advanced C++ Metaprogramming CreateSpace Independent Publishing Platform ISBN: Auflage, Juni 2011

43 C++ SFINAE / 44  Links principle inf.mpg.de/~kettner/courses/lib_design_03/notes/meta.html#Constraining sfinae.html

44 C++ SFINAE / 44 Fragen?


Herunterladen ppt "C++ SFINAE / 44 C++ SFINAE inkl. std::enable_if Detlef Wilkening 08.01.2015."

Ähnliche Präsentationen


Google-Anzeigen