Transcript Folie 1

C++ SFINAE
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
 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(43L);
// => 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(43L);
// => 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(43L);
// => 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
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 ein einfaches Beispiel
http://www.wilkening-online.de
20 / 44
C++ SFINAE
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>
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 NoInit
{
};
struct WithInit
{
void init() {}
};
cout << boolalpha;
cout << "NoInit
-> " << HasInitFct<NoInit>::result;
cout << "WithInit -> " << HasInitFct<WithInit>::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
• In Abhängigkeit von HasInitFct<>::result den Typ wählen
•
TMP If
• Aufruft der Funktion über den Typ
http://www.wilkening-online.de
24 / 44
C++ SFINAE
struct InitNo
{
template<class T> static void init(T&)
{
}
};
struct InitYes
{
template<class T> static void init(T& t)
{
t.init();
}
};
http://www.wilkening-online.de
25 / 44
C++ SFINAE
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);
}
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 NoInit
{
};
struct WithInit
{
void init() {}
};
struct InheritedInit : WithInit
{
};
struct DoubleInheritedInit : InheritedInit
{
};
http://www.wilkening-online.de
28 / 44
C++ SFINAE
cout
cout
cout
cout
cout
<<
<<
<<
<<
<<
boolalpha;
"NoInit
->
"WithInit ->
" InhInit ->
"DInhInit ->
"
"
"
"
<<
<<
<<
<<
HasInitFct<NoInit>::result;
HasInitFct<WithInit>::result;
HasInitFct<Inhe...Init>::result;
HasInitFct<Doubl...Init>::result;
=>
NoInit
WithInit
InhInit
DInhInit
->
->
->
->
false
true
false
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 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);
};
http://www.wilkening-online.de
31 / 44
C++ SFINAE
cout
cout
cout
cout
cout
<<
<<
<<
<<
<<
boolalpha;
"NoInit
->
"WithInit ->
" InhInit ->
"DInhInit ->
"
"
"
"
<<
<<
<<
<<
HasRealInitFct<NoInit>::result;
HasRealInitFct<WithInit>::result;
HasRealInitFct<I...Init>::result;
HasRealInitFct<D...Init>::result;
=>
NoInit
WithInit
InhInit
DInhInit
->
->
->
->
false
true
true
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%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
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 << "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();
}
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 << "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();
}
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 << "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();
}
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 << "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();
}
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_trivial

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
CreateSpace 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%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.mpiinf.mpg.de/~kettner/courses/lib_design_03/notes/meta.html#Constraining
http://flamingdangerzone.com/cxx11/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