Wie funktioniert „void_t“?

Lesezeit: 12 Minuten

Wie funktioniert „void t
Unsinn

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
  • has_member< B > wertet zu false_type

http://ideone.com/HCTlBb

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

1647084014 133 Wie funktioniert „void t
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 Muster Terlaubt 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 Muster void_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<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };

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

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

tritt ein. Wir bekommen:

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:

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

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?

  • – dyp 30. Dezember 2014 um 16:27 Uhr

    W/r/t Standard-Template-Argumente,

    open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2008

  • – 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

    obwohl.

1647084014 339 Wie funktioniert „void t
– Jarod42

6. Dezember 2021 um 8:41 Uhr

Benutzer3059627 Dieser Thread und der Thread SFINAE: Das Verständnis von void_t und detect_if hat mich gerettet. Ich möchte das Verhalten an einigen Beispielen demonstrieren:

Das Werkzeug:

struct A {
    using type = int;
};

struct B{
    using type = void;
}

cppinsights

auto f = has_type_member<float>::value;
auto a = has_type_member<A>::value;
auto b = has_type_member<B>::value;

Um die Implementierung nach Floattypen und folgenden Typen zu testen:

Getestet von Standardimplementierung

#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

  1. Ausgabe: has_type_member<float>
  2. Dieser Fall zeigt, dass der Compiler: has_type_member<float, void>
  3. 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

#include <type_traits>

template< class, class = void >
struct has_type_member : std::false_type { };

template<class T>
struct has_type_member<T, typename T::type>: std::true_type {}; 

Eine Spezialisierung dieser Signatur gefunden und den Wert erhalten

bool f = false;
bool a = false;
bool b = true;

Fall 3

  1. has_type_member<float> Ausgabe: has_type_member<float, void>Fall f
  2. wurde abgeschlossen typename float::type .
  3. Dann versuchte es der Compiler

und gescheitert.

  1. has_type_member<A> Die primäre ausgewählte Vorlage. has_type_member<A, void>
  2. Fall a has_type_member<A, typename A::type> wurde abgeschlossen has_type_member<A, int>
  3. Dann Compiler versucht has_type_member<A, void>
  4. und fand heraus, dass es war

Der Compiler entschied, dass es sich nicht um eine Spezialisierung von handelte

  1. has_type_member<B> Dann primäre Vorlage ausgewählt. has_type_member<B, void>Fall b
  2. wurde abgeschlossen has_type_member<B, typename B::type> . has_type_member<B, void>Dann Compiler versucht
  3. und fand heraus, dass es war has_type_member<B, void>
  4. true_type .

Der Compiler entschied, dass es sich um eine Spezialisierung von handelte

#include <type_traits>

//int as default 2nd argument
template< class, class = int >
struct has_type_member : std::false_type { };

template<class T>
struct has_type_member<T, std::void<typename T::type>>: std::true_type {}; 

abgeholt.

bool f = false;
bool a = false;
bool b = false;

Fall 4 has_type_member<T> Ausgabe: has_type_member<T, int> Die true_type ist vom Typ has_type_member<T, void> für alle 3 Variablen, während die

hat Signatur als

wenn es gültig ist. std::void_tFazit

  1. Also, die T::type :
  2. Überprüfen Sie, ob

993480cookie-checkWie funktioniert „void_t“?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy