2014-12-29 7 views
104

Ich beobachtete Walter Browns Vortrag bei Cppcon14 über moderne Template Programmierung (Part I, Part II) wo er seine void_t SFINAE Technik präsentierte.Wie funktioniert `void_t`?

Beispiel:
eine einfache variable Vorlage gegeben, die void auswertet, wenn alle Vorlagenargumente gut gebildet werden:

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

und die folgende Eigenschaft, die für die Existenz einer Membervariable Mitglied genannt 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 versuchte zu verstehen, warum und wie das funktioniert. Daher ist 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 wohlgeformte
    • void_t<> gültig ist und ausgewertet void
  • has_member< A , void > und deshalb wählt er die spezielle Vorlage
  • has_member< T , void > und ausgewertet true_type

2.has_member<B>

  • has_member< B , void_t< decltype(B::member) > >
    • B::member nicht
    • decltype(B::member) ist schlecht ausgebildet und nicht stumm (sfinae) als Standardargument
    • has_member< B , expression-sfinae > so diese Vorlage verworfen
  • Compiler findet has_member< B , class = void > mit Leere existiert
  • has_member<B> ausgewertet false_type

http://ideone.com/HCTlBb

Fragen:
1. Ist mein Verständnis davon korrekt?
2. Walter Brown gibt an, dass das Standardargument genau dem Typ entsprechen muss, der in void_t verwendet wurde, damit es funktioniert. Warum das? (Ich sehe nicht, warum diese Typen übereinstimmen müssen, macht nicht jeder Standardtyp den Job?

)
+5

Anzeige 2) Stellen Sie sich vor, die statische Assert wurde geschrieben als: 'has_member :: value'. Dann kann die Teilspezialisierung, die 'has_member 'ergibt, nicht übereinstimmen. Daher muss es 'has_member :: value' sein, oder, mit syntaktischem Zucker, ein Standardargument vom Typ' void'. – dyp

+1

@dyp Danke, ich werde das bearbeiten. Mh, ich sehe keine Notwendigkeit, 'has_member ' standardmäßig in 'void' zu haben. Angenommen, dieses Merkmal wird zu einem beliebigen Zeitpunkt nur mit einem Vorlagenargument verwendet, dann könnte das Standardargument ein beliebiger Typ sein? – nonsensation

+0

Interessante Frage. – cybermonkey

Antwort

93

Wenn Sie has_member<A>::value schreiben, der Compiler nachschlägt der Name has_member und findet die primären Klassenvorlage, das heißt, diese Erklärung: (. im OP, ist, dass als Definition geschrieben)

template< class , class = void > 
struct has_member; 

Die Vorlagenargumentliste <A> wird mit der Vorlagenparameterliste dieser primären Vorlage verglichen. Da die primäre Vorlage über zwei Parameter verfügt, von denen Sie jedoch nur einen Parameter angegeben haben, wird als Standardwert für den verbleibenden Parameter das Standardschablonenargument verwendet: void. Es ist, als ob du has_member<A, void>::value geschrieben hättest.

Jetzt wird die Vorlagenparameterliste mit allen Spezialisierungen der Vorlage has_member verglichen. Nur wenn keine Spezialisierung übereinstimmt, wird die Definition der primären Vorlage als Fallback verwendet. So ist die partielle Spezialisierung berücksichtigt wird:

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

Der Compiler versucht, die Vorlage Argumente A, void mit den Mustern in der Teil Spezialisierung definiert anzupassen: T und void_t<..> eins nach dem anderen. Zuerst wird eine Vorlagenargumentableitung durchgeführt. Die obige Teilspezialisierung ist immer noch eine Vorlage mit Template-Parametern, die durch Argumente "gefüllt" werden müssen. Das erste Muster, T, ermöglicht dem Compiler, den Template-Parameter T abzuleiten. Dies ist eine triviale Ableitung, aber betrachten Sie ein Muster wie T const&, wo wir noch T ableiten könnten. Für das Muster T und das Vorlagenargument A folgern wir T zu A.

Im zweiten Muster void_t< decltype(T::member) > erscheint der Vorlagenparameter T in einem Kontext, in dem er nicht aus einem Vorlagenargument abgeleitet werden kann. Es gibt zwei Gründe dafür:

  • Der Ausdruck in decltype ausdrücklich von Template-Argument Abzug ausgeschlossen. Ich denke, das liegt daran, dass es beliebig komplex sein kann.

  • Selbst wenn wir ein Muster ohne decltype wie void_t<T> verwenden, dann wird der Abzug von T geschieht auf der Alias-Vorlage aufgelöst. Das heißt, wir lösen die Aliasvorlage auf und versuchen dann, aus dem resultierenden Muster den Typ T abzuleiten.Das resultierende Muster ist jedoch void, das nicht von T abhängig ist und es daher nicht erlaubt, einen spezifischen Typ für T zu finden. Dies ist ähnlich dem mathematischen Problem, eine konstante Funktion zu invertieren (im mathematischen Sinne dieser Begriffe).

Vorlage Argument Abzug ist abgeschlossen (*), jetzt die abgeleitet Vorlage Argumente ersetzt werden. Dadurch entsteht eine Spezialisierung, die wie folgt aussieht:

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

Der Typ void_t< decltype(A::member) > > jetzt ausgewertet werden können. Es ist nach der Substitution gut gebildet, daher tritt kein Substitutionsfehler auf. Wir erhalten:

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

Jetzt können wir die Vorlage Parameterliste dieser Spezialisierung mit den Schablonen Argumente auf den ursprünglichen has_member<A>::value geliefert vergleichen. 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 sind mit der gleichen Spezialisierung am Ende:

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

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


(*) The Standard, verwechslungs IMHO umfasst den Substitutionsprozess und die Anpassung des explizit angegebene Vorlage Arguments in dem Argumente Vorlage Abzug Prozess. Zum Beispiel (post-N4296) [temp.class.spec.match]/2:

Eine partielle Spezialisierung entspricht eine gegebene tatsächliche Vorlage Argumentliste , wenn die Vorlage Argumente der partiellen Spezialisierung können aus der abgeleitet werden tatsächliche Vorlagenargumentliste

Aber dies bedeutet nicht nur bedeuten, dass alle Template-Parameter der partiellen Spezialisierung werden abgeleitet haben; es bedeutet auch, dass die Substitution erfolgreich sein muss und (wie es scheint?) die Template-Argumente mit den (substituierten) Template-Parametern der Teilspezialisierung übereinstimmen müssen. Beachten Sie, dass mir nicht vollständig bekannt ist, wobei der Standard den Vergleich zwischen der Liste der ersetzten Argumente und der Liste der übergebenen Argumente angibt.

+0

Ich kann nicht einmal herausfinden, in welchem ​​Schritt der Standard die Standard-Template-Argumente der primären (Klassen-) Vorlage in die Template-Argumentliste einfügt Teilspezialisierung :( – dyp

+0

Danke! Ich habe es immer und immer wieder gelesen, und ich denke, meine Gedanken darüber, wie Vorlage Argument Abzug genau funktioniert und was der Compiler für die endgültige Vorlage wählt, ist im Moment nicht korrekt. – nonsensation

+3

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#697 –

14
// 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 nur existiert, wenn es gut ausgebildet ist, so dass, wenn decltype(T::member) gültig ist und nicht eindeutig. die Spezialisierung ist so für has_member<T , void> als Zustand im Kommentar.

Wenn Sie has_member<A> schreiben, ist es has_member<A, void> wegen Standardvorlage Argument.

Und wir haben Spezialisierung für has_member<A, void> (so von true_type erben), aber wir haben keine Spezialisierung für has_member<B, void> (so wir die Standard-Definition verwenden: erben von false_type)