Ich habe Walter Browns Vortrag auf der Cppcon14 über moderne Vorlagenprogrammierung ( Teil I , Teil II ) gesehen, in dem er seine void_t
SFINAE-Technik vorstellte.
Beispiel:
Bei einer einfachen Variablenvorlage, die bewertet, void
ob alle Vorlagenargumente gut geformt sind:
template< class ... > using void_t = void;
und das folgende Merkmal, das die Existenz einer Mitgliedsvariablen namens member prü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. 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
existiertdecltype( A::member )
ist wohlgeformtvoid_t<>
ist gültig und wertet ausvoid
has_member< A , void >
und deshalb wählt es die spezielle Vorlagehas_member< T , void >
und bewertet zutrue_type
2. has_member< B >
has_member< B , void_t< decltype( B::member ) > >
B::member
ist nicht vorhandendecltype( B::member )
ist schlecht geformt und versagt lautlos (sfinae)has_member< B , expression-sfinae >
Daher wird diese Vorlage verworfen
- Compiler findet
has_member< B , class = void >
mit void als Standardargument has_member< B >
bewertet zufalse_type
Fragen:
1. Ist mein Verständnis davon richtig?
2. Walter Brown gibt an, dass das Standardargument genau der Typ sein muss, in dem es verwendet wird, void_t
damit es funktioniert. Warum ist das so? (Ich verstehe nicht, warum diese Typen übereinstimmen müssen, erledigt nicht irgendein Standardtyp den Job?)
has_member<A,int>::value
. Dann kann die Teilspezialisierung, die ausgewertet wird,has_member<A,void>
nicht übereinstimmen. Daher muss eshas_member<A,void>::value
oder mit syntaktischem Zucker ein Standardargument vom Typ seinvoid
.has_member< T , class = void >
Verzug zu seinvoid
. Angenommen, dieses Merkmal wird zu jedem Zeitpunkt nur mit 1 Vorlagenargument verwendet, dann könnte das Standardargument ein beliebiger Typ sein?template <class, class = void>
zutemplate <class, class = void_t<>>
. Jetzt können wir mit dervoid_t
Implementierung der Alias-Vorlage tun, was wir wollen :)Antworten:
1. Vorlage für die Primärklasse
Wenn Sie schreiben
has_member<A>::value
, sucht der Compiler nach dem Namenhas_member
und findet die primäre Klassenvorlage, dh diese Deklaration:(Im OP ist das als Definition geschrieben.)
Die Liste
<A>
der Vorlagenargumente wird mit der Liste der Vorlagenparameter 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 Standardvorlagenargument verwendet :void
. Es ist, als hättest du geschriebenhas_member<A, void>::value
.2. Spezialisierte Klassenvorlage
Jetzt wird die Vorlagenparameterliste mit allen Spezialisierungen der Vorlage verglichen
has_member
. Nur wenn keine Spezialisierung übereinstimmt, wird die Definition der primären Vorlage als Ersatz verwendet. Die Teilspezialisierung wird also berücksichtigt:Der Compiler versucht, die Vorlagenargumente
A, void
mit den in der Teilspezialisierung definierten Mustern abzugleichen:T
und zwarvoid_t<..>
nacheinander. Zunächst wird der Abzug der Vorlagenargumente 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 abzuleitenT
. Dies ist ein trivialer Abzug, aber betrachten Sie ein Muster wieT const&
, aus dem wir noch ableiten könntenT
. Für das MusterT
und das Template - ArgumentA
, folgern wirT
seinA
.Im zweiten Muster
void_t< decltype( T::member ) >
erscheint der Template-ParameterT
in einem Kontext, in dem er aus keinem Template-Argument abgeleitet werden kann.Der Abzug der Vorlagenargumente ist abgeschlossen (*) , jetzt werden die abgeleiteten Vorlagenargumente ersetzt. Dadurch entsteht eine Spezialisierung, die folgendermaßen aussieht:
Der Typ
void_t< decltype( A::member ) >
kann jetzt ausgewertet werden. Es ist nach der Substitution gut ausgebildet, daher tritt kein Substitutionsfehler auf. Wir bekommen:3. Wahl
Jetzt können wir die Vorlagenparameterliste dieser Spezialisierung mit den Vorlagenargumenten vergleichen, die dem Original zur Verfügung gestellt wurden
has_member<A>::value
. Beide Typen stimmen genau überein, daher wird diese Teilspezialisierung gewählt.Auf der anderen Seite, wenn wir die Vorlage definieren als:
Wir haben die gleiche Spezialisierung:
aber unsere Vorlagenargumentliste für
has_member<A>::value
jetzt ist<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 verwirrenderweise den Ersetzungsprozess und den Abgleich explizit angegebener Vorlagenargumente in den Vorlagenargument-Abzugsprozess . Zum Beispiel (nach N4296) [temp.class.spec.match] / 2:
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 Vorlagenargumente mit den (substituierten) Vorlagenparametern der Teilspezialisierung übereinstimmen müssen. Beachten Sie, dass mir nicht vollständig bekannt ist, wo der Standard den Vergleich zwischen der Liste der ersetzten Argumente und der Liste der bereitgestellten Argumente angibt.
quelle
Diese obige Spezialisierung existiert nur, wenn sie gut geformt ist, also wenn sie
decltype( T::member )
gültig und nicht mehrdeutig ist. Die Spezialisierung ist sohas_member<T , void>
wie im Kommentar angegeben.Wenn Sie schreiben
has_member<A>
, liegt dieshas_member<A, void>
an dem Standardvorlagenargument.Und wir haben Spezialisierung für
has_member<A, void>
(also erben vontrue_type
), aber wir haben keine Spezialisierung fürhas_member<B, void>
(also verwenden wir die Standarddefinition: erben vonfalse_type
)quelle