webentwicklung-frage-antwort-db.com.de

Wie funktioniert "void_t"?

Ich habe Walter Browns Vortrag auf der Cppcon14 über moderne Template-Programmierung ( Teil I , Teil II ) gesehen, in dem er seine void_t - SFINAE-Technik vorstellte.

Beispiel:
Bei einer einfachen Variablenvorlage, die zu void ausgewertet wird, wenn alle Vorlagenargumente korrekt formuliert sind:

template< class ... > using void_t = void;

und das folgende Merkmal, das die Existenz einer Mitgliedsvariablen namens member überprüft:

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. Deshalb 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<> Ist gültig und ergibt void
  • has_member< A , void > Und wählt daher die spezialisierte Vorlage
  • has_member< T , void > Und ergibt true_type

2. has_member< B >

  • has_member< B , void_t< decltype( B::member ) > >
    • B::member Existiert nicht
    • decltype( B::member ) ist schlecht geformt und versagt lautlos (sfinae)
    • has_member< B , expression-sfinae > Diese Vorlage wird verworfen
  • der Compiler findet has_member< B , class = void > mit void als Standardargument
  • has_member< B > Ergibt false_type

http://ideone.com/HCTlBb

Fragen:
1. Ist mein Verständnis davon korrekt?
2. Walter Brown gibt an, dass das Standardargument genau der gleiche Typ sein muss wie das in void_t Verwendete, damit es funktioniert. Warum das? (Ich verstehe nicht, warum diese Typen übereinstimmen müssen, macht der Job nicht einfach irgendeinen Standardtyp?)

137
nonsensation

Wenn Sie has_member<A>::value Schreiben, sucht der Compiler nach dem Namen has_member Und findet die primäre Klassenvorlage, dh 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 enthält, Sie jedoch nur einen angegeben haben, wird für den verbleibenden Parameter standardmäßig das Standardargument für die Vorlage verwendet: void. Es ist, als hätten Sie has_member<A, void>::value Geschrieben.

Jetzt wird die Liste der Vorlagenparameter mit den Spezialisierungen der Vorlage verglichen has_member. Nur wenn keine Spezialisierung übereinstimmt, wird die Definition der primären Vorlage als Ersatz verwendet. So wird die Teilspezialisierung berücksichtigt:

template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };

Der Compiler versucht, die Vorlagenargumente A, void Nacheinander mit den in der Teilspezialisierung definierten Mustern abzugleichen: T und void_t<..>. Zunächst wird die Ableitung von Vorlagenargumenten durchgeführt. Die obige Teilspezialisierung ist immer noch eine Vorlage mit Vorlagenparametern, die mit Argumenten "gefüllt" werden müssen.

Das erste Muster, T, ermöglicht es dem Compiler, den Template-Parameter T abzuleiten. Dies ist ein trivialer Abzug, aber betrachten Sie ein Muster wie T const&, Aus dem wir noch T ableiten können. Für das Muster T und das Template-Argument A wird T zu A hergeleitet.

Im zweiten Muster void_t< decltype( T::member ) > erscheint der Template-Parameter T in einem Kontext, in dem aus keinem Template-Argument abgeleitet werden kann. Dafür gibt es zwei Gründe:

  • Der Ausdruck innerhalb von decltype wird explizit vom Abzug von Vorlagenargumenten ausgeschlossen. Ich denke, das liegt daran, dass es beliebig komplex sein kann.

  • Selbst wenn wir ein Muster ohne decltype wie void_t< T > Verwendet haben, erfolgt der Abzug von T für die aufgelöste Aliasvorlage. Das heißt, wir lösen die Aliasvorlage auf und versuchen dann, den Typ T aus dem resultierenden Muster abzuleiten. Das resultierende Muster ist jedoch void, was nicht von T abhängt und es uns daher nicht erlaubt, einen bestimmten Typ für T zu finden. Dies ähnelt dem mathematischen Problem des Versuches, eine konstante Funktion zu invertieren (im mathematischen Sinne dieser Begriffe).

Der Abzug der Vorlagenargumente ist abgeschlossen(*), jetzt werden die deduced Template-Argumente ersetzt. Dies schafft eine Spezialisierung, die so aussieht:

template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };

Der Typ void_t< decltype( A::member ) > > kann nun ausgewertet werden. Es ist nach der Substitution gut geformt, daher tritt kein Substitutionsfehler auf. Wir bekommen:

template<>
struct has_member<A, void> : true_type
{ };

Jetzt können wir die Liste der Vorlagenparameter dieser Spezialisierung mit den Vorlagenargumenten vergleichen, die dem Original has_member<A>::value Übergeben wurden. Beide Typen stimmen genau überein, daher wird diese Teilspezialisierung gewählt.

Auf der anderen Seite, wenn wir die Vorlage definieren als:

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

Wir haben die gleiche Spezialisierung:

template<>
struct has_member<A, void> : true_type
{ };

unsere Liste der Template-Argumente für has_member<A>::value lautet jetzt <A, int>. Die Argumente stimmen nicht mit den Parametern der Spezialisierung überein, und die primäre Vorlage wird als Ersatz ausgewählt.


(*) Der Standard enthält, meiner Meinung nach verwirrend, den Substitutionsprozess und den Abgleich explizit angegebener Vorlagenargumente im Prozess Vorlagenargumentabzug. Zum Beispiel (nach N4296) [temp.class.spec.match]/2:

Eine Teilspezialisierung stimmt mit einer gegebenen tatsächlichen Vorlagenargumentliste überein, wenn die Vorlagenargumente der Teilspezialisierung aus der tatsächlichen Vorlagenargumentliste abgeleitet werden können.

Dies bedeutet aber nicht nur, dass alle Template-Parameter der Teilspezialisierung abgeleitet werden müssen; Dies bedeutet auch, dass die Substitution erfolgreich sein muss und (wie es scheint?) die Template-Argumente mit den (substituierten) Template-Parametern der partiellen Spezialisierung übereinstimmen müssen. Beachten Sie, dass mir where nicht vollständig bekannt ist. Der Standard gibt den Vergleich zwischen der Liste der ersetzten Argumente und der Liste der bereitgestellten Argumente an.

119
dyp
// specialized as has_member< T , void > or discarded (sfinae)
template<class T>
struct has_member<T , void_t<decltype(T::member)>> : true_type
{ };

Die obige Spezialisierung existiert nur, wenn sie gut ausgebildet ist, also wenn decltype( T::member ) gültig und nicht mehrdeutig ist. Die Spezialisierung gilt für has_member<T , void> als Status im Kommentar.

Wenn Sie has_member<A> Schreiben, ist es has_member<A, void> Wegen des Standardvorlagenarguments.

Und wir haben eine Spezialisierung für has_member<A, void> (Also von true_type Erben), aber wir haben keine Spezialisierung für has_member<B, void> (Also verwenden wir die Standarddefinition: von false_type)

17
Jarod42