Sound1.01 and Environmental Noise Presented to the Young RMLA by Siiri Wilkening.
C++ SFINAE / 44 C++ SFINAE inkl. std::enable_if Detlef Wilkening 08.01.2015.
-
Upload
odilia-striffler -
Category
Documents
-
view
104 -
download
0
Transcript of C++ SFINAE / 44 C++ SFINAE inkl. std::enable_if Detlef Wilkening 08.01.2015.
C++ SFINAE
http://www.wilkening-online.de 1 / 44
C++ SFINAEinkl. std::enable_if
Detlef Wilkeninghttp://www.wilkening-online.de08.01.2015
C++ SFINAE
http://www.wilkening-online.de 2 / 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 § 14.8.2 den Sachverhalt• Ohne aber ein explizites Acronym zu verwenden
C++ SFINAE
http://www.wilkening-online.de 3 / 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
C++ SFINAE
http://www.wilkening-online.de 4 / 44
Das ist alles - " substitution failure is not an error" Fertig
C++ SFINAE
http://www.wilkening-online.de 5 / 44
Das ist alles - " substitution failure is not an error" Fertig
Okay, ein paar Beispiele und Anwendungen sind wohl noch ganz hilfreich…
C++ SFINAE
http://www.wilkening-online.de 6 / 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<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
C++ SFINAE
http://www.wilkening-online.de 7 / 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<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
C++ SFINAE
http://www.wilkening-online.de 8 / 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<class T> void print(typename T::type t)
{
cout << "T::type: " << t << endl;
}
// => Kein Problem
C++ SFINAE
http://www.wilkening-online.de 9 / 44
Ganz nebenbei: Wie spricht man unser neues Funktions-Template an?
C++ SFINAE
http://www.wilkening-online.de 10 / 44
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;
}
C++ SFINAE
http://www.wilkening-online.de 11 / 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<A>(true); // => T::type: true
}
C++ SFINAE
http://www.wilkening-online.de 12 / 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<class T> typename T::type print(T t)
{
cout << "T=>: " << t << endl;
}
C++ SFINAE
http://www.wilkening-online.de 13 / 44
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;
}
C++ SFINAE
http://www.wilkening-online.de 14 / 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)
C++ SFINAE
http://www.wilkening-online.de 15 / 44
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)
{
}
C++ SFINAE
http://www.wilkening-online.de 16 / 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 § 14.8.2
C++ SFINAE
http://www.wilkening-online.de 17 / 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
C++ SFINAE
http://www.wilkening-online.de 18 / 44
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
C++ SFINAE
http://www.wilkening-online.de 19 / 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<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
C++ SFINAE
http://www.wilkening-online.de 20 / 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
C++ SFINAE
http://www.wilkening-online.de 21 / 44
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);
};
C++ SFINAE
http://www.wilkening-online.de 22 / 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)
C++ SFINAE
http://www.wilkening-online.de 23 / 44
struct NoInit
{
};
struct WithInit
{
void init() {}
};
cout << boolalpha;
cout << "NoInit -> " << HasInitFct<NoInit>::result; // => false
cout << "WithInit -> " << HasInitFct<WithInit>::result; // => true
C++ SFINAE
http://www.wilkening-online.de 24 / 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
C++ SFINAE
http://www.wilkening-online.de 25 / 44
struct InitNo{ template<class T> static void init(T&) { }};
struct InitYes{ template<class T> static void init(T& t) { t.init(); }};
C++ SFINAE
http://www.wilkening-online.de 26 / 44
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);}
C++ SFINAE
http://www.wilkening-online.de 27 / 44
Und was ist das Problem mit der Vererbung?
C++ SFINAE
http://www.wilkening-online.de 28 / 44
struct NoInit{};
struct WithInit{ void init() {}};
struct InheritedInit : WithInit{};
struct DoubleInheritedInit : InheritedInit{};
C++ SFINAE
http://www.wilkening-online.de 29 / 44
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 -> falseWithInit -> true InhInit -> falseDInhInit -> false
C++ SFINAE
http://www.wilkening-online.de 30 / 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
C++ SFINAE
http://www.wilkening-online.de 31 / 44
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);};
C++ SFINAE
http://www.wilkening-online.de 32 / 44
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 -> falseWithInit -> true InhInit -> trueDInhInit -> true
C++ SFINAE
http://www.wilkening-online.de 33 / 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"• 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
C++ SFINAE
http://www.wilkening-online.de 34 / 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 <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
C++ SFINAE
http://www.wilkening-online.de 35 / 44
template<class T>
typename enable_if<is_pod<T>::value, void>::type reset(T& t)
{
cout << "POD\n";
memset(&t, 0, sizeof(T));
}
template<class T>
typename enable_if<!is_pod<T>::value, void>::type reset(T& t)
{
cout << "Kein POD\n";
t.reset();
}
C++ SFINAE
http://www.wilkening-online.de 36 / 44
struct A
{
A() {}
void reset() { s=""; }
string s;
};
int main()
{
int n;
reset(n); // => POD
A a;
reset(a); // => Kein POD
}
C++ SFINAE
http://www.wilkening-online.de 37 / 44
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));
}
template<class T>
typename enable_if<!is_pod<T>::value>::type reset(T& t)
{
cout << "Kein POD\n";
t.reset();
}
C++ SFINAE
http://www.wilkening-online.de 38 / 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<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));
}
template<class T>
typename enable_if_t<!is_pod<T>::value> reset(T& t)
{
cout << "Kein POD\n";
t.reset();
}
C++ SFINAE
http://www.wilkening-online.de 39 / 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<class T> void reset(T& t,
typename enable_if<is_pod<T>::value, T>::type* = nullptr)
{
cout << "POD\n";
memset(&t, 0, sizeof(T));
}
template<class T> void reset(T& t,
typename enable_if<!is_pod<T>::value, T>::type* = nullptr)
{
cout << "Kein POD\n";
t.reset();
}
C++ SFINAE
http://www.wilkening-online.de 40 / 44
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>
{
};
C++ SFINAE
http://www.wilkening-online.de 41 / 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. § 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_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
…
C++ SFINAE
http://www.wilkening-online.de 42 / 44
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• CreateSpace Independent Publishing Platform• ISBN: 978-1460966167• 1. Auflage, Juni 2011
C++ SFINAE
http://www.wilkening-online.de 43 / 44
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
C++ SFINAE
http://www.wilkening-online.de 44 / 44
Fragen?