Die Präsentation wird geladen. Bitte warten

Die Präsentation wird geladen. Bitte warten

C++ SFINAE inkl. std::enable_if

Ähnliche Präsentationen


Präsentation zum Thema: "C++ SFINAE inkl. std::enable_if"—  Präsentation transkript:

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

2 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 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 Das ist alles - " substitution failure is not an error"
Fertig

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

6 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; int n = 42; print(n); // => T: 42 print(&n); // => T*: 42 print(43L); // => l: 43

7 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 int n = 42; print(n); // => T: 42 print(&n); // => T*: 42 print(43L); // => l: 43

8 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

9 Ganz nebenbei: Wie spricht man unser neues Funktions-Template an?

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

11 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<A>(true); // => T::type: true }

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

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

14 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 Beispiel für Array der Größe 0
Beispiel von cppreference // 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)

16 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 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 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

19 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

20 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 template<typename T> struct HasInitFct
{ typedef char Yes[1]; typedef char No[2]; template<typename U, U> struct SignatureCheck; template<typename V> static Yes& check(SignatureCheck<void(T::*)(), &V::init>*); template<typename> static No& check(...); static bool const result = (sizeof(check<T>(0)) == 1); };

22 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 struct NoInit { }; struct WithInit void init() {} cout << boolalpha; cout << "NoInit -> " << HasInitFct<NoInit>::result; // => false cout << "WithInit -> " << HasInitFct<WithInit>::result; // => true

24 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 struct InitNo { template<class T> static void init(T&) } }; struct InitYes template<class T> static void init(T& t) t.init();

26 template<bool Expr, class TrueType, class FalseType>
struct TypeIf { typedef TrueType Type; }; template<class TrueType, class FalseType> struct TypeIf<false, TrueType, FalseType> typedef FalseType Type; template<class T> void init (T& t) typedef typename TypeIf<HasInitFct<T>::result, InitYes, InitNo>::Type Type; Type::init(t); }

27 Und was ist das Problem mit der Vererbung?

28 struct NoInit { }; struct WithInit void init() {} struct InheritedInit : WithInit struct DoubleInheritedInit : InheritedInit

29 cout << boolalpha;
cout << "NoInit -> " << HasInitFct<NoInit>::result; cout << "WithInit -> " << HasInitFct<WithInit>::result; cout << " InhInit -> " << HasInitFct<Inhe...Init>::result; cout << "DInhInit -> " << HasInitFct<Doubl...Init>::result; => NoInit -> false WithInit -> true InhInit -> false DInhInit -> false

30 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 template<typename T> struct HasRealInitFct
{ typedef char Yes[1]; typedef char No[2]; struct Mixin { void init(){} }; struct Mixed : public T, public Mixin {}; template<typename U, U> struct SigCheck; template<typename V> static No& check(SigCheck<void(Mixin::*)(), &V::init>*); template<typename> static Yes& check(...); static bool const result = (sizeof(check<Mixed>(0))==1); };

32 cout << boolalpha;
cout << "NoInit -> " << HasRealInitFct<NoInit>::result; cout << "WithInit -> " << HasRealInitFct<WithInit>::result; cout << " InhInit -> " << HasRealInitFct<I...Init>::result; cout << "DInhInit -> " << HasRealInitFct<D...Init>::result; => NoInit -> false WithInit -> true InhInit -> true DInhInit -> true

33 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 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

35 template<class T>
typename enable_if<is_pod<T>::value, void>::type reset(T& t) { cout << "POD\n"; memset(&t, 0, sizeof(T)); } typename enable_if<!is_pod<T>::value, void>::type reset(T& t) cout << "Kein POD\n"; t.reset();

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

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

38 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 << "POD\n"; memset(&t, 0, sizeof(T)); } typename enable_if_t<!is_pod<T>::value> reset(T& t) cout << "Kein POD\n"; t.reset();

39 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 << "POD\n"; memset(&t, 0, sizeof(T)); } typename enable_if<!is_pod<T>::value, T>::type* = nullptr) cout << "Kein POD\n"; t.reset();

40 Mögliche Implementierung von "enable_if"
Von cppreference.com template<bool B, class T = void> struct enable_if { typedef T type; };   template<class T> struct enable_if<false, T> };

41 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_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_reference is_arithmetic is_fundamental is_object is_scalar is_compound is_member_pointer is_const is_volatile is_trivial is_trivially_copyable 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

42 Bücher Nicolai M. Josuttis & David Vandevoorde Davide Di Gennaro
C++ Templates The Complete Guide Addison-Wesley Longman ISBN: 1. 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: 1. Auflage, Juni 2011

43 Links

44 Fragen?


Herunterladen ppt "C++ SFINAE inkl. std::enable_if"

Ähnliche Präsentationen


Google-Anzeigen