C++ SFINAE inkl. std::enable_if

Slides:



Advertisements
Ähnliche Präsentationen
der Universität Oldenburg
Advertisements

Klassen - Verkettete Liste -
Abstrakte Klassen Basisklassen sollten in der Regel sehr allgemein sein. Oft ist es nicht notwendig, dass Objekte dieser generellen Basisklassen angelegt.
Konzepte objektorientierter Systeme
Universität Dortmund, Lehrstuhl Informatik 1 EINI II Einführung in die Informatik für Naturwissenschaftler und Ingenieure.
C Tutorium – Fehlerbehandlung – Knut Stolze. 2 Grundsatz Also ist auch nach jedem Funktionsaufruf auf Fehler zu prüfen!! Jeder(!) Funktionsaufruf kann.
Author: Mitarbeiter Entwicklung. Author: Unsere Situation Teilen Sie die negativen Nachrichten mit. Seien Sie deutlich.
Programmierkurs C/C++
Seminar: "Einführung in C/C++" Einführung in die Programmiersprache C/C++ Donnerstag Andreas Döring SS 2004.
Java: Objektorientierte Programmierung
Java: Dynamische Datentypen
Indirekte Adressierung
Java: Referenzen und Zeichenketten
Java: Grundlagen der Objektorientierung
Konstruktoren.
Objekte und Arbeitsspeicher
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Kapitel 7 Claudio Moraga, Gisbert Dittrich FBI Unido
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Vorlesung 2 SWS WS 99/00 Gisbert Dittrich FBI Unido
Universität Dortmund, Lehrstuhl Informatik 1 EINI II Einführung in die Informatik für Naturwissenschaftler und Ingenieure.
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Vorlesung 2 SWS WS 99/00 Gisbert Dittrich FBI Unido
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Kapitel 10 Claudio Moraga; Gisbert Dittrich FBI Unido
Imperative Programmierung Funktionen und Parameter
PKJ 2005/1 Stefan Dissmann Zusammenfassung Bisher im Kurs erarbeitete Konzepte(1): Umgang mit einfachen Datentypen Umgang mit Feldern Umgang mit Referenzen.
Zusammenfassung Vorwoche
14StrukturKlasse1 Von der Struktur zur Klasse von C zu C++ Von Jens Hoffmann
Portierung von Java nach C
DVG Methoden 1 Methoden. 2 int dezi = Integer.parseInt(args[0]); boolean vz = (dezi>=0); dezi = Math.abs(dezi); String Bin = ""; do { } while.
Wir müssen also überlegen: Implementierung der Knoten, Implementierung der Kanten, daraus: Implementierung des Graphen insgesamt. Annahme: die Knoteninhalte.
Rekursive Funktionen (Fakultät)
Einführung in die Programmierung
Einführung in die Programmierung Wintersemester 2009/10 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund.
Einführung in die Programmierung Wintersemester 2012/13 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund.
Einführung in die Programmierung Wintersemester 2008/09 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund.
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fakultät.
Einführung in die Programmierung Wintersemester 2008/09 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund.
Einführung in die Programmierung Wintersemester 2009/10 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund.
Einführung in die Informatik für Naturwissenschaftler und Ingenieure
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fachbereich.
Einführung in die Programmierung Wintersemester 2009/10 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund.
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fachbereich.
Einführung in die Programmierung Wintersemester 2008/09 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund.
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fachbereich.
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fakultät.
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fachbereich.
Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung) (Vorlesung) Prof. Dr. Günter Rudolph Fachbereich.
Einführung in die Programmiersprache C 4
Informatik 1 Letzte Übung.
Abteilung für Telekooperation Übung Softwareentwicklung 1 für Wirtschaftsinformatik Dr. Wieland Schwinger
Dynamische Datentypen
Lindenmayer-Systeme: Fraktale rekursiv zeichnen
Referenztypen (II) und Const- Referenzen Temporäre Objekte, Const- Referenzen, was genau ist konstant?
EPROG Tutorium #4 Philipp Effenberger
EPROG Tutorium #5 Philipp Effenberger
Datentypen Überblick Datentypen Einfache / fundamentale Datentypen
C++11 Defaulted & Deleted Functions / 48 C++11 Defaulted & Deleted Functions Detlef Wilkening
Konversionwww.journalistenakademie.de1/15 Konversionsorientiertes Schreiben Im Direktmarketing spricht man von „Konversion“, wenn man eine bestimmte Aktion.
Polymorphie (Vielgestaltigkeit). Wenn eine Methode, wie z.B. print für verschiedene Programmteile steht (und z.B. einmal Objekte verschiedener Klassen.
early binding (frühe Bindung) late binding (späte Bindung)
A) Erklären Sie den Datentyp char. b) Erklären Sie den Datentyp Struct c) Erklären Sie die Wirkungsweise des Operators & bei Anwendung im Zusammenhang.
Java Syntaxdiagramme Buchstabe A B Z a z ... Ziffer
1 // Laengste Zeile bestimmen // // Liest die Eingabe Zeichen fuer Zeichen, bis // das erscheint. // Die laengste Zeile wird ausgegeben (bei.
Ausnahmen Vorlesung besteht aus zwei Teilen Programmierung (4 SWS)
ROBERT NYSTROM GAME PROGRAMMING PATTERNS III.8 Thema: Sequencing Patterns Seminar: Softwaretechnologie II (Teil 2) Dozent: Prof. Dr. Manfred Thaller Referent:
Namensräume (namespaces). verwendet man umfangreiche eigene und fremde Bibliotheken (Sammlungen von Funktionen) so ist die Wahrscheinlichkeit groß, daß.
Variablen und Operatoren. C++ Teil 2: Grundstrukturen Variablen Operatoren Kontrollstrukturen Funktionen Header-Dateien Pointer und Referenzen.
Tutorium Software-Engineering SS14 Florian Manghofer.
Tutorium Software-Engineering SS14 Florian Manghofer.
IOStreamLibrary.
IOStreamLibrary.
Einführung in die Programmierung
 Präsentation transkript:

C++ SFINAE inkl. std::enable_if Detlef Wilkening http://www.wilkening-online.de 08.01.2015

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

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

Das ist alles - " substitution failure is not an error" Fertig

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

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

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

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

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

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;

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 }

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

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

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)

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)

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

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

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

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

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

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

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)

struct NoInit { }; struct WithInit void init() {} cout << boolalpha; cout << "NoInit -> " << HasInitFct<NoInit>::result; // => false cout << "WithInit -> " << HasInitFct<WithInit>::result; // => true

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

struct InitNo { template<class T> static void init(T&) } }; struct InitYes template<class T> static void init(T& t) t.init();

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

Und was ist das Problem mit der Vererbung?

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

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

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

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

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

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%2B%2B_Idioms Aber es gibt Idioms, die auf SFINAE aufsetzen Z.B. "Member Detector" http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Member_Detector Ein einfaches Beispiel dafür hatten wir gerade Oder Enable-If Siehe nächste Folien

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

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();

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

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();

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();

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();

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

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 …

Bücher Nicolai M. Josuttis & David Vandevoorde Davide Di Gennaro 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 CreateSpace Independent Publishing Platform ISBN: 978-1460966167 1. Auflage, Juni 2011

Links http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error http://en.wikibooks.org/wiki/More_C%2B%2B_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.cplusplus-soup.com/2006/09/learning-about-sfinae.html http://www.ddj.com/cpp/184401659 http://www.semantics.org/once_weakly/w02_SFINAE.pdf http://people.mpi-inf.mpg.de/~kettner/courses/lib_design_03/notes/meta.html#Constraining http://flamingdangerzone.com/cxx11/2013/02/11/to-sfinae-or-not-to-sfinae.html

Fragen?