Qual è esattamente il “contesto immediato” menzionato nello standard C ++ 11 per il quale si applica la SFINAE?

Il paragrafo 14.8.2 / 8 della norma C ++ 11 specifica le condizioni in base alle quali un errore di sostituzione deve o non deve comportare un errore di compilazione “difficile” (causando così il fallimento della compilazione) o un errore “soft” che fare in modo che il compilatore scarti un modello da un gruppo di candidati per la risoluzione del sovraccarico (senza fare in modo che la compilazione fallisca e abilitando il ben noto linguaggio SFINAE):

Se una sostituzione produce un tipo o un’espressione non valida, digitare la deduzione non riesce. Un tipo o un’espressione non valida è uno che sarebbe mal formato se scritto usando gli argomenti sostituiti. [Nota: il controllo dell’accesso viene eseguito come parte del processo di sostituzione. -End note] Solo i tipi e le espressioni non validi nel contesto immediato del tipo di funzione e i suoi tipi di parametri del modello possono causare un errore di deduzione . […]

Le parole ” contesto immediato ” appaiono solo 8 volte nell’intero standard C ++ 11, e ogni volta sono seguite da (o si verificano come parte di) un’istanza del seguente testo (non normativo):

[Nota: la valutazione dei tipi e delle espressioni sostituite può provocare effetti collaterali come l’istanziazione delle specializzazioni di modelli di class e / o specializzazioni di modelli di funzioni, la generazione di funzioni implicitamente definite, ecc. Tali effetti collaterali non sono “immediati” contesto “e può portare a una malformazione del programma. -End note]

La nota fornisce un suggerimento (non molto generoso) su cosa si intende per contesto immediato , ma almeno per me questo spesso non è sufficiente per decidere se una sostituzione è o non dovrebbe causare un errore di compilazione “difficile”.

DOMANDA:

Potresti fornire una spiegazione, una procedura di decisione e / o alcuni esempi concreti per aiutare a capire in quali casi un errore di sostituzione si verifica e non si verifica nel ” contesto immediato ” del tipo di funzione e dei suoi tipi di parametri del modello?

Se si considerano tutti i modelli e le funzioni implicitamente definiti che sono necessari per determinare il risultato della sostituzione degli argomenti del modello e si immagina che vengano generati per primi, prima che inizi la sostituzione, allora qualsiasi errore che si verifica in quel primo passaggio non si trova nel contesto immediato, e causare errori duri.

Se tutte le istanze e le definizioni implicite (che potrebbero includere la definizione di funzioni come eliminate) possono essere eseguite senza errori, quindi qualsiasi ulteriore “errore” che si verifica durante la sostituzione (ovvero quando si fa riferimento ai modelli istanziati e alle funzioni implicitamente definite nel modello di funzione firma) non sono errori, ma comportano errori di deduzione.

Quindi, dato un modello di funzione come questo:

template void func(typename T::type* arg); 

e un “fall-back” che verrà usato se la deduzione fallisce per l’altra funzione:

 template void func(...); 

e un modello di class come questo:

 template struct A { typedef T* type; }; 

Una chiamata a func>(nullptr) sostituirà A per T e per verificare se T::type esiste deve istanziare A . Se immaginiamo di mettere un’istanza esplicita prima della chiamata a func(nullptr) :

 template class A; 

allora quello non funzionerebbe, perché prova a creare il tipo int&* e i puntatori ai riferimenti non sono permessi. Non arriviamo al punto di verificare se la sostituzione ha esito positivo, perché c’è un errore grave nell’istanziare A .

Ora diciamo che c’è una specializzazione esplicita di A :

 template<> struct A { }; 

Una chiamata a func>(nullptr) richiede l’istanziazione di A , quindi immagina un’istanza esplicita da qualche parte nel programma prima della chiamata:

 template class A; 

Questa istanziazione è OK, non ci sono errori da questo, quindi procediamo alla sostituzione degli argomenti. L’istanziazione di A funzionato, ma A::type non esiste, ma va bene perché è referenziato solo nella dichiarazione di func , quindi causa solo la deduzione degli argomenti per fallire e il fall-back ... viene chiamata invece la funzione.

In altre situazioni la sostituzione potrebbe causare la definizione implicita di funzioni membro speciali, possibilmente come eliminate, che potrebbero innescare altre istanziazioni o definizioni implicite. Se si verificano errori durante la fase di “generazione di istanze e definizioni implicite”, allora si tratta di errori, ma se questo riesce ma durante la sostituzione un’espressione nella firma del modello di funzione risulta non valida, ad esempio perché utilizza un membro che non esiste o qualcosa che è stato implicitamente definito come cancellato, non è un errore, ma solo un errore di deduzione.

Quindi il modello mentale che uso è che la sostituzione deve prima fare un passo di “preparazione” per generare tipi e membri, che potrebbero causare errori gravi, ma una volta che abbiamo fatto tutta la generazione necessaria, eventuali altri usi non validi non sono errori. Naturalmente tutto ciò che fa è spostare il problema da “cosa significa contesto immediato ?” a “Quali tipi e membri devono essere generati prima che questa sostituzione possa essere verificata?” quindi può o non può aiutarti!

Il contesto immediato è fondamentalmente quello che vedi nella dichiarazione del modello stesso. Tutto al di fuori di questo è un errore difficile. Esempi di errori difficili:

 #include  template struct trait{ using type = typename T::type; }; template::type> void f(int); void f(...); template void g(int); void g(...); template struct dependent_false : std::false_type{}; template struct X{ static_assert(dependent_false(), "..."); using type = void; }; int main(){ f(0); g>(0); } 

Versione live.