1. Modello di classe primaria
Quando scrivi has_member<A>::value
, il compilatore cerca il nome has_member
e trova il modello di classe principale , ovvero questa dichiarazione:
template< class , class = void >
struct has_member;
(Nel PO, è scritto come una definizione.)
L'elenco degli argomenti del modello <A>
viene confrontato con l'elenco dei parametri del modello di questo modello principale. Dal momento che il modello primario ha due parametri, ma è fornito solo uno, il parametro rimanente viene default all'argomento modello predefinito: void
. È come se avessi scritto has_member<A, void>::value
.
2. Modello di classe specializzato
Ora , l'elenco dei parametri del modello viene confrontato con qualsiasi specializzazione del modello has_member
. Solo se nessuna specializzazione corrisponde, la definizione del modello primario viene utilizzata come fallback. Quindi la specializzazione parziale viene presa in considerazione:
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : true_type
{ };
Il compilatore tenta di abbinare gli argomenti del modello A, void
con i modelli definiti nella specializzazione parziale: T
e void_t<..>
uno per uno. Innanzitutto , viene eseguita la deduzione dell'argomento modello. La specializzazione parziale sopra è ancora un modello con parametri modello che devono essere "riempiti" da argomenti.
Il primo modello T
consente al compilatore di dedurre il parametro template T
. Questa è una deduzione banale, ma considera un modello come T const&
, dove potremmo ancora dedurlo T
. Per lo schema T
e l'argomento modello A
, deduciamo T
essere A
.
Nel secondo modello void_t< decltype( T::member ) >
, il parametro template T
appare in un contesto in cui non può essere dedotto da nessun argomento template.
Ci sono due ragioni per questo:
L'espressione all'interno decltype
è esplicitamente esclusa dalla deduzione dell'argomento template. Immagino che ciò sia dovuto al fatto che può essere arbitrariamente complesso.
Anche se abbiamo usato un modello senza decltype
like void_t< T >
, la deduzione di T
avviene sul modello di alias risolto. Cioè, risolviamo il modello di alias e successivamente proviamo a dedurre il tipo T
dal modello risultante. Il modello risultante, tuttavia, è void
, che non dipende da T
e quindi non ci consente di trovare un tipo specifico per T
. Questo è simile al problema matematico del tentativo di invertire una funzione costante (nel senso matematico di quei termini).
Detrazione argomento di un template è finito (*) , ora le dedotte argomenti template vengono sostituiti. Questo crea una specializzazione simile a questa:
template<>
struct has_member< A, void_t< decltype( A::member ) > > : true_type
{ };
Il tipo void_t< decltype( A::member ) >
può ora essere valutato. È ben formato dopo la sostituzione, quindi non si verifica alcun errore di sostituzione . Noi abbiamo:
template<>
struct has_member<A, void> : true_type
{ };
3. Scelta
Ora , possiamo confrontare l'elenco dei parametri del modello di questa specializzazione con gli argomenti del modello forniti all'originale has_member<A>::value
. Entrambi i tipi corrispondono esattamente, quindi viene scelta questa specializzazione parziale.
D'altra parte, quando definiamo il modello come:
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
{ };
Finiamo con la stessa specializzazione:
template<>
struct has_member<A, void> : true_type
{ };
ma il nostro elenco di argomenti modello per has_member<A>::value
ora lo è <A, int>
. Gli argomenti non corrispondono ai parametri della specializzazione e il modello principale viene scelto come fallback.
(*) Lo Standard, IMHO in modo confuso, include il processo di sostituzione e la corrispondenza degli argomenti del modello esplicitamente specificati nel processo di deduzione degli argomenti del modello . Ad esempio (post-N4296) [temp.class.spec.match] / 2:
Una specializzazione parziale corrisponde a un determinato elenco di argomenti del modello effettivo se gli argomenti del modello della specializzazione parziale possono essere dedotti dall'elenco degli argomenti del modello effettivo.
Ma ciò non significa solo che tutti i parametri modello della specializzazione parziale debbano essere dedotti; significa anche che la sostituzione deve avere esito positivo e (come sembra?) gli argomenti del modello devono corrispondere ai parametri (sostituiti) del modello della specializzazione parziale. Si noti che non sono completamente consapevole di dove lo standard specifica il confronto tra l'elenco degli argomenti sostituiti e l'elenco degli argomenti fornito.
has_member<A,int>::value
. Quindi, la specializzazione parziale che valutahas_member<A,void>
non può corrispondere. Pertanto, deve esserehas_member<A,void>::value
, o, con zucchero sintattico, un argomento predefinito di tipovoid
.