Ich habe Walter Browns Vortrag auf der Cppcon14 über moderne Template-Programmierung gesehen (Teil I, Teil II), wo er seine präsentierte void_t SFINAE-Technik.
Beispiel:
Bei einer einfachen Variablenvorlage, die ausgewertet wird void wenn alle Template-Argumente wohlgeformt sind:
template< class ... > using void_t = void;
und die folgende Eigenschaft, die das Vorhandensein einer Member-Variablen mit dem Namen überprüft Mitglied:
template< class , class = void >
struct has_member : std::false_type
{ };
// specialized as has_member< T , void > or discarded (sfinae)
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : std::true_type
{ };
Ich habe versucht zu verstehen, warum und wie das funktioniert. Daher ein kleines Beispiel:
class A {
public:
int member;
};
class B {
};
static_assert( has_member< A >::value , "A" );
static_assert( has_member< B >::value , "B" );
1.has_member< A >
has_member< A , void_t< decltype( A::member ) > >
A::member existiert
decltype( A::member ) ist wohlgeformt
void_t<> gültig ist und ausgewertet wird void
has_member< A , void > und deshalb wählt es die spezialisierte Vorlage
has_member< T , void > und wertet zu true_type
2.has_member< B >
has_member< B , void_t< decltype( B::member ) > >
B::member ist nicht vorhanden
decltype( B::member ) ist schlecht geformt und versagt lautlos (sfinae)
has_member< B , expression-sfinae > also wird diese Vorlage verworfen
Compiler findet has_member< B , class = void > mit void als Standardargument
Fragen:
1. Ist mein Verständnis davon richtig?
2. Walter Brown gibt an, dass das Standardargument genau derselbe Typ sein muss wie der, der in verwendet wird void_t damit es funktioniert. Warum ist das so? (Ich verstehe nicht, warum diese Typen übereinstimmen müssen, funktioniert nicht jeder Standardtyp?)
Ad 2) Stellen Sie sich vor, das statische Assert wäre geschrieben als: has_member<A,int>::value. Dann die partielle Spezialisierung, die ausgewertet wird has_member<A,void> kann nicht passen. Daher muss es sein has_member<A,void>::valueoder, mit syntaktischem Zucker, ein Standardargument vom Typ void.
– dyp
29. Dezember 2014 um 10:57 Uhr
@dyp Danke, ich werde das bearbeiten. Mh, ich sehe keine Notwendigkeit darin zu haben has_member< T , class = void > in Verzug geraten void noch. Angenommen, diese Eigenschaft wird zu jedem Zeitpunkt nur mit 1 Vorlagenargument verwendet, könnte das Standardargument dann ein beliebiger Typ sein?
– Unsinn
29. Dezember 2014 um 11:29 Uhr
Interessante Frage.
– AStopher
29. Dezember 2014 um 12:12 Uhr
Beachten Sie, dass in diesem Vorschlag open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4436.pdfWalter hat sich verändert template <class, class = void> zu template <class, class = void_t<>>. Jetzt können wir also machen, was wir wollen void_t Alias-Template-Implementierung 🙂
– Johannes Koch
8. März 2018 um 17:33 Uhr
dyp
1. Primärklassenvorlage
Wenn du schreibst has_member<A>::valuesucht der Compiler nach dem Namen has_member und findet die primär Klassenvorlage, also diese Deklaration:
template< class , class = void >
struct has_member;
(Im OP ist das als Definition geschrieben.)
Die Vorlagenargumentliste <A> wird mit der Vorlagenparameterliste dieser primären Vorlage verglichen. Da die primäre Vorlage zwei Parameter hat, Sie aber nur einen angegeben haben, wird der verbleibende Parameter standardmäßig auf das Standardvorlagenargument gesetzt: void. Es ist, als hättest du geschrieben has_member<A, void>::value.
2. Spezialisierte Klassenvorlage
Jetztwird die Vorlagenparameterliste mit allen Spezialisierungen der Vorlage verglichen has_member. Nur wenn keine Spezialisierung passt, wird die Definition des primären Templates als Fallback verwendet. Die Teilspezialisierung wird also berücksichtigt:
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
Der Compiler versucht, die Template-Argumente abzugleichen A, void mit den in der Teilspezialisierung definierten Mustern: T und void_t<..> Einer nach dem anderen. Zuerst, wird eine Vorlagenargumentableitung durchgeführt. Die obige partielle Spezialisierung ist immer noch ein Template mit Template-Parametern, die mit Argumenten “gefüllt” werden müssen.
Das erste MusterTerlaubt dem Compiler, den Template-Parameter abzuleiten T. Dies ist eine triviale Schlussfolgerung, aber betrachten Sie ein Muster wie T const&wo wir noch ableiten könnten T. Für das Muster T und das Template-Argument Aleiten wir ab T zu sein A.
Im zweiten Mustervoid_t< decltype( T::member ) >der Template-Parameter T erscheint in einem Kontext, in dem es nicht aus irgendeinem Musterargument abgeleitet werden kann.
Dafür gibt es zwei Gründe:
Der innere Ausdruck decltype ist explizit von der Ableitung von Template-Argumenten ausgeschlossen. Ich denke, das liegt daran, dass es beliebig komplex sein kann.
Auch wenn wir ein Muster ohne verwendet haben decltype wie void_t< T >dann der Abzug von T geschieht auf der aufgelösten Aliasvorlage. Das heißt, wir lösen das Alias-Template auf und versuchen später, den Typ abzuleiten T aus dem resultierenden Muster. Das resultierende Muster ist jedoch voiddie nicht abhängig ist T und erlaubt uns daher nicht, einen bestimmten Typ für zu finden T. Dies ähnelt dem mathematischen Problem, eine konstante Funktion (im mathematischen Sinne dieser Begriffe) zu invertieren.
Die Ableitung von Vorlagenargumenten ist beendet, jetzt der abgeleitet
Template-Argumente werden ersetzt. Dadurch entsteht eine Spezialisierung, die wie folgt aussieht: void_t< decltype( A::member ) > Der Typ kann jetzt ausgewertet werden. Es ist nach der Substitution wohlgeformt, daher nein Substitutionsfehler
3. WahlJetzt has_member<A>::valuekönnen wir die Template-Parameterliste dieser Spezialisierung mit den Template-Argumenten vergleichen, die dem Original übergeben wurden
. Beide Typen passen genau zusammen, daher wird diese Teilspezialisierung gewählt.
template< class , class = int > // <-- int here instead of void
struct has_member : false_type
{ };
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
Auf der anderen Seite, wenn wir die Vorlage wie folgt definieren:
Wir landen bei der gleichen Spezialisierung: has_member<A>::value aber unsere Vorlagenargumentliste für <A, int>jetzt ist
. Die Argumente stimmen nicht mit den Parametern der Spezialisierung überein, und die primäre Vorlage wird als Fallback ausgewählt. Der Standard, meiner Meinung nach verwirrend, beinhaltet den Substitutionsprozess und den Abgleich explizit angegebener Template-Argumente in der [temp.class.spec.match]Vorlagenargumentabzug
Prozess. Zum Beispiel (nach N4296)
/2: Eine partielle Spezialisierung passt zu einer gegebenen aktuellen Template-Argumentliste, wenn die Template-Argumente der partiellen Spezialisierung aus der tatsächlichen Template-Argumentliste abgeleitet werden können. Aber dies nicht gerade bedeuten, dass alle Template-Parameter der partiellen Spezialisierung abgeleitet werden müssen; es bedeutet auch, dass die Substitution erfolgreich sein muss und (wie es scheint?) die Template-Argumente mit den (ersetzten) Template-Parametern der partiellen Spezialisierung übereinstimmen müssen. Beachten Sie, dass ich nicht ganz bewusst bin
wo
der Standard spezifiziert den Vergleich zwischen der ersetzten Argumentliste und der gelieferten Argumentliste.
Danke! Ich habe es immer wieder gelesen, und ich denke, meine Vorstellung davon, wie die Argumentableitung von Vorlagen genau funktioniert und was der Compiler für die endgültige Vorlage auswählt, ist im Moment nicht korrekt.
– Unsinn
30. Dezember 2014 um 12:12 Uhr
@JohannesSchaub-litb Danke! Das ist allerdings etwas deprimierend. Gibt es wirklich keine Regeln für den Abgleich eines Template-Arguments mit einer Spezialisierung? Auch nicht für explizite Spezialisierungen?
– TC 28. Januar 2015 um 10:41 Uhr @dyp Ein paar Wochen später und viel darüber gelesen und mit einem Hinweis von
dieser Ausschnitt
Ich glaube, ich fange an zu verstehen, wie das funktioniert. Ihre Erklärung macht für mich von Lesen zu Lesen mehr Sinn, danke!
– Unsinn 25. Februar 2015 um 23:05 Uhr Ich wollte hinzufügen, dass der Begriff
primär
Template war der Schlüssel (die Templates begegnen uns zum ersten Mal im Code)
// specialized as has_member< T , void > or discarded (sfinae)
template<class T>
struct has_member<T , void_t<decltype(T::member)>> : true_type
{ };
– Unsinn decltype( T::member ) 27. September 2016 um 12:51 Uhr has_member<T , void> Diese oben genannte Spezialisierung existiert nur, wenn sie gut ausgebildet ist, also wann
ist gültig und nicht mehrdeutig. die Spezialisierung ist so z has_member<A>wie im Kommentar angegeben. has_member<A, void> Wenn du schreibst
es ist has_member<A, void> wegen des Standard-Template-Arguments. true_typeUnd wir haben Spezialisierung für has_member<B, void> (also erben von false_type), aber wir haben keine Spezialisierung für
(Also verwenden wir die Standarddefinition: erben von void_t<decltype(T::member)>> ) decltype(T::member, void())Damit
ist nichts anderes als eine standardisierte/klarere Schreibweise
?
– 303 void_t 5. Dezember 2021 um 21:26 Uhr
@303: Einige (alte) Compiler-Versionen hatten Schwierigkeiten, SFINAE mit (einigen Implementierungen von) anzuwenden
#include <type_traits>
// primary template handles types that have no nested ::type member:
template< class, class = int >
struct has_type_member : std::false_type { };
// specialization recognizes types that do have a nested ::type member:
template< class T >
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
Von
bool f = false;
bool a = true;
bool b = true;
bool x = has_type_member<A, int>::value; //x = false;
diese std::void_t-Referenz
#include <type_traits>
// primary template handles types that have no nested ::type member:
template< class, class = int >
struct has_type_member : std::false_type { };
template< class T >
struct has_type_member<T, void> : std::true_type { };
// specialization recognizes types that do have a nested ::type member:
template< class T >
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
Ausgabe
/home/insights/insights.cpp:14:8: error: redefinition of 'has_type_member<T, std::void_t<typename T::type>>'
struct has_type_member<T, std::void_t<typename T::type>> : std::true_type { };
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/insights/insights.cpp:8:8: note: previous definition is here
struct has_type_member<T, void>: std::true_type {};
^
1 error generated.
Error while processing /home/insights/insights.cpp.
Fall 1 has_type_member<T, std::void_t<typename T::type>> Ausgabe has_type_member Damit, has_type_member<T, void>definiert eine Spezialisierung von
und die Signatur ist genau
#include <type_traits>
template< class, class = void >
struct has_type_member : std::false_type { };
// specialize 2nd type as void
template< class T>
struct has_type_member<T, void> : std::true_type { };
.
bool f = true;
bool a = true;
bool b = true;
Fall 2
Ausgabe: has_type_member<float>
Dieser Fall zeigt, dass der Compiler: has_type_member<float, void>
Wollte eine Übereinstimmung finden für std::true_type
Herausgefunden, dass die Vorlage 2 Argumente erfordert, dann das 2. Argument mit Standardargumenten gefüllt. Die Struktur war wie
Ad 2) Stellen Sie sich vor, das statische Assert wäre geschrieben als:
has_member<A,int>::value
. Dann die partielle Spezialisierung, die ausgewertet wirdhas_member<A,void>
kann nicht passen. Daher muss es seinhas_member<A,void>::value
oder, mit syntaktischem Zucker, ein Standardargument vom Typvoid
.– dyp
29. Dezember 2014 um 10:57 Uhr
@dyp Danke, ich werde das bearbeiten. Mh, ich sehe keine Notwendigkeit darin zu haben
has_member< T , class = void >
in Verzug geratenvoid
noch. Angenommen, diese Eigenschaft wird zu jedem Zeitpunkt nur mit 1 Vorlagenargument verwendet, könnte das Standardargument dann ein beliebiger Typ sein?– Unsinn
29. Dezember 2014 um 11:29 Uhr
Interessante Frage.
– AStopher
29. Dezember 2014 um 12:12 Uhr
Beachten Sie, dass in diesem Vorschlag open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4436.pdfWalter hat sich verändert
template <class, class = void>
zutemplate <class, class = void_t<>>
. Jetzt können wir also machen, was wir wollenvoid_t
Alias-Template-Implementierung 🙂– Johannes Koch
8. März 2018 um 17:33 Uhr