2016-04-08 40 views
18

Perché questo codice di lavoro:SFINAE funziona in modo diverso in casi di tipo e il modello non-parametri di tipo

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

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

e in grado di distinguere correttamente tra queste due chiamate:

Add(1); 
Add(1.0); 

mentre il seguente codice se risultati compilati nel ridefinizione di Add() errore?

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

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

Quindi, se il parametro di modello è di tipo, allora abbiamo ridefinizione della funzione, se si tratta di non-tipo, allora tutto è ok.

+7

Immaginate di scrivere due "overload" di funzione come questi: 'void Add (int = 0);' e 'void Add (int = 1)'. Sono davvero diversi? – Constructor

+1

Possibile duplicato di [SFINAE per la funzione membro della classe (uno non compila l'altro)] (https://stackoverflow.com/questions/31625914/sfinae-for-class-member-function-one-compiles-the-other-non) – neonxc

risposta

1

Penso che il problema sia il fatto che è possibile utilizzare una funzione anche se un parametro di modello 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.

0

SFINAE non si estende 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.

2

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

Così, quando si scrive:

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

va bene, ma quando si scrive:

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

Stai ridefinire il primo modello add(), solo che questa volta si specifica un diverso tipo di default per il secondo parametro del template: alla fine, hai definito un overload per add() con la stessa identica firma, quindi l'errore.

Se si desidera avere diverse implementazioni come suggerisce la domanda, è necessario utilizzare std::enable_if_t come parametro di ritorno del modello o utilizzarlo nello stesso modo del primo esempio. Così il vostro codice iniziale diventa:

template<typename T> 
std::enable_if_t<std::is_same<T, int>::value> Add(T) {} 
template<typename T> 
std::enable_if_t<!std::is_same<T, int>::value> Add(T) {} 

esempio Lavorare su Coliru

Il codice di cui sopra, se T == int, la seconda firma diventa invalido e che scatena SFINAE.

NB: Si supponga 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!

+0

@Constructor Sì, ho dimenticato di aggiungere '!' Sul secondo modello. Fisso! – Rerito

17

SFINAE riguarda la sostituzione. Quindi sostituiamoci!

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

template< 
    typename T, 
    std::enable_if_t<!std::is_same<T, int>::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) {} 

Rimuovere fallimenti otteniamo:

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

Ora parametro di modello rimuovere i valori:

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

Questi sono diversi modelli.

Ora quello che scombina:

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

template< 
    typename T, 
    typename = typename std::enable_if<!std::is_same<T, int>::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 fallimenti:

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

Ed ora i valori dei parametri del modello:

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

Queste sono le stesse firme 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.

0

Proverò a fornire prima 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.

si hanno due funzioni dichiarate come tali:

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

E

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

Speriamo che ci si rende conto che questo non riuscirà a compilare, loro non sta andando mai essere un modo per distinguere tra i due chiama allo foo con l'argomento predefinito. È completamente ambiguo, come potrà mai sapere il compilatore quale predefinito scegliere? Potresti chiederti perché ho usato questo esempio, dopotutto non dovresti il ​​modello nel primo esempio dedurre tipi diversi? La risposta breve è che nessuno, e questo perché la "firma" dei due modelli nel secondo esempio:

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

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

...avere l'esatto firma stessa, che è:

template<typename T,typename> 
void Add(T); 

E (rispettivamente)

template <typename T, typename> 
void Add(T); 

Ora si dovrebbe iniziare a capire la somiglianza è tra l'esempio che ho dato con i non-modelli e l'esempio che hai fornito che fallito.