SFINAE funziona in modo diverso nei casi di parametri modello tipo e non tipo

Perché questo codice funziona:

template< typename T, std::enable_if_t<std::is_same::value, T>* = nullptr> void Add(T) {} template< typename T, std::enable_if_t<!std::is_same::value, T>* = nullptr> void Add(T) {} 

e può distinguere correttamente tra queste due chiamate:

 Add(1); Add(1.0); 

mentre il seguente codice, se compilato, comporta la ridefinizione dell’errore di Add () ?

 template< typename T, typename = typename std::enable_if<std::is_same::value, T>::type> void Add(T) {} template< typename T, typename = typename std::enable_if<!std::is_same::value, T>::type> void Add(T) {} 

Quindi se il parametro template è type, allora abbiamo ridefinizione della funzione, se è non-type, allora tutto è ok.

SFINAE riguarda la sostituzione. Quindi sostituiamoci!

 template< typename T, std::enable_if_t::value, T>* = nullptr> void Add(T) {} template< typename T, std::enable_if_t::value, T>* = nullptr> void Add(T) {} 

diventa:

 template< class T=int, int* = nullptr> void Add(int) {} template< class T=int, Substitution failure* = nullptr> void Add(int) { template< class T=double, Substitution failure* = nullptr> void Add(double) {} template< class T=double double* = nullptr> void Add(double) {} 

Rimuovi i fallimenti che otteniamo:

 template< class T=int, int* = nullptr> void Add(int) {} template< class T=double double* = nullptr> void Add(double) {} 

Ora rimuovi i valori dei parametri del modello :

 template< class T, int*> void Add(T) {} template< class T double*> void Add(T) {} 

Questi sono diversi modelli.

Ora quello che incasina:

 template< typename T, typename = typename std::enable_if::value, T>::type> void Add(T) {} template< typename T, typename = typename std::enable_if::value, T>::type> void Add(T) {} 

diventa:

 template< typename T=int, typename =int> void Add(int) {} template< typename int, typename = Substitution failure > void Add(int) {} template< typename T=double, typename = Substitution failure > void Add(double) {} template< typename T=double, typename = double> void Add(double) {} 

Rimuovi errori:

 template< typename T=int, typename =int> void Add(int) {} template< typename T=double, typename = double> void Add(double) {} 

E ora i valori dei parametri del modello:

 template< typename T, typename> void Add(T) {} template< typename T, typename> void Add(T) {} 

Queste sono la stessa firma del modello. E questo non è permesso, errore generato.

Perché esiste una regola del genere? Oltre lo scopo di questa risposta. Sto semplicemente dimostrando come i due casi sono diversi e affermando che lo standard li tratta in modo diverso.

Quando si utilizza un parametro di modello non di tipo come sopra, si modifica la firma del modello non solo i valori dei parametri del modello. Quando si utilizza un parametro del modello di tipo come sopra, si modificano solo i valori dei parametri del modello.

Qui, il problema è che la firma del modello per add() è la stessa: un modello di funzione che accetta due tipi di parametri.

Quindi quando scrivi:

 template< typename T, typename = std::enable_if_t::value, T>> void Add(T) {} 

Va bene, ma quando scrivi:

 template< typename T, typename = std::enable_if_t::value, T>> void Add(T) {} 

Stai ridefinendo il primo modello add() , solo questa volta specifichi un diverso tipo predefinito per il secondo parametro del template: alla fine hai definito un overload per add() con la stessa identica firma, quindi l’errore.

Se vuoi avere diverse implementazioni come suggerisce la tua domanda, dovresti usare std::enable_if_t come parametro di ritorno del tuo modello o usarlo nello stesso modo del tuo primo esempio. Quindi il tuo codice iniziale diventa:

 template std::enable_if_t::value> Add(T) {} template std::enable_if_t::value> Add(T) {} 

Esempio di lavoro su Coliru

Nel codice sopra, se T == int , la seconda firma diventa non valida e questo innesca SFINAE.

NB: Supponiamo di voler implementare N. Puoi usare lo stesso trucco di cui sopra ma dovrai assicurarti che solo un booleano tra N sia vero e che l’N-1 rimanga falso, altrimenti otterrai lo stesso identico errore!

Penso che il problema sia il fatto che puoi usare una funzione anche se un parametro template predefinito non viene compilato specificando un valore diverso per esso. Pensa a cosa succederebbe se specificassi due parametri del modello in una chiamata da aggiungere.

Proverò a dare un esempio senza l’uso di modelli, ma con argomenti predefiniti. L’esempio seguente è paragonabile al perché il secondo esempio non ha esito positivo, sebbene non sia indicativo del funzionamento interno della risoluzione di sovraccarico del modello.

Hai due funzioni dichiarate come tali:

 void foo(int _arg1, int _arg2 = 3); 

E

 void foo(int _arg1, int _arg2 = 4); 

Spero che tu ti renda conto che questo non riuscirà a compilare, il loro non sarà mai un modo per distinguere tra le due chiamate to foo con l’argomento predefinito. È completamente ambiguo, come mai il compilatore saprà quale default scegliere? Potresti chiederti perché ho usato questo esempio, dopotutto non dovresti il ​​modello nel primo esempio dedurre tipi diversi? La risposta breve è no, e questo è dovuto alla “firma” dei due modelli nel secondo esempio:

 template< typename T, typename = typename std::enable_if::value, T>::type> void Add(T) {} template< typename T, typename = typename std::enable_if::value, T>::type> void Add(T) {} 

… hanno la stessa identica firma, che è:

 template void Add(T); 

E (rispettivamente)

 template  void Add(T); 

Ora dovresti iniziare a capire la somiglianza tra l’esempio che ho fornito con non-templates e l’esempio che hai fornito che non è riuscito.

SFINAE non si diffonde ai valori predefiniti né per i tipi né per i valori. Solo i tipi di argomenti e risultati della funzione vengono utilizzati in questa tecnica.