1. Vorlage für die Primärklasse
Wenn Sie schreiben has_member<A>::value
, 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 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 geschrieben has_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:
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
Der Compiler versucht, die Vorlagenargumente A, void
mit den in der Teilspezialisierung definierten Mustern abzugleichen: T
und zwar void_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 abzuleiten T
. Dies ist ein trivialer Abzug, aber betrachten Sie ein Muster wie T const&
, aus dem wir noch ableiten könnten T
. Für das Muster T
und das Template - Argument A
, folgern wir T
sein A
.
Im zweiten Muster void_t< decltype( T::member ) >
erscheint der Template-Parameter T
in einem Kontext, in dem er aus keinem Template-Argument abgeleitet werden kann.
Dafür gibt es zwei Gründe:
Der Ausdruck im Inneren decltype
wird ausdrücklich vom Abzug von Vorlagenargumenten ausgeschlossen. Ich denke, das liegt daran, dass es beliebig komplex sein kann.
Auch wenn verwenden wir ein Muster ohne decltype
wie void_t< T >
, dann Abzug T
geschieht auf der Alias - Vorlage aufgelöst. Das heißt, wir lösen die Alias-Vorlage auf und versuchen später, den Typ T
aus dem resultierenden Muster abzuleiten . Das resultierende Muster ist jedoch, void
das nicht abhängig ist T
und es uns daher nicht erlaubt, einen bestimmten Typ für zu finden T
. Dies ähnelt dem mathematischen Problem des Versuchs, eine konstante Funktion zu invertieren (im mathematischen Sinne dieser Begriffe).
Der Abzug der Vorlagenargumente ist abgeschlossen (*) , jetzt werden die abgeleiteten Vorlagenargumente ersetzt. Dadurch entsteht eine Spezialisierung, die folgendermaßen aussieht:
template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };
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:
template<>
struct has_member<A, void> : true_type
{ };
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:
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
{ };
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:
Eine Teilspezialisierung entspricht einer bestimmten Liste der tatsächlichen Vorlagenargumente, wenn die Vorlagenargumente der Teilspezialisierung aus der Liste der tatsächlichen Vorlagenargumente 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 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.
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
.