Equivalenza tra modelli di funzioni e modelli di funzioni abbreviate
Tutti i riferimenti standard di seguito si riferiscono all'attuale bozza di lavoro standard ISO , generata il 22-06-2020.
[dcl.fct]/18 afferma che [estratto, corsivo mio]:
Un modello di funzione abbreviato è una dichiarazione di funzione che ha uno o più segnaposto di tipo di parametro generico ([dcl.spec.auto]). Un template di funzione abbreviato è equivalente a un template di funzione ([temp.fct]) il cui template-parameter-list include un template-parameter di tipo inventato per ogni segnaposto di tipo di parametro generico della dichiarazione di funzione, in ordine di apparizione. [...]
Tale che le seguenti dichiarazioni di funzione sono areuably equivalenti :
template <typename T>
void f(T);
void f(auto); // re-declaration
Possiamo notare, tuttavia, che l'esempio di [dcl.fct]/18 afferma che
[...]
Queste dichiarazioni sono funzionalmente equivalenti ( ma non equivalenti ) alle seguenti dichiarazioni.
[...]
che può discutibilmente (non sono sicuro di come interpretarlo) in conflitto con l'affermazione di equivalenza nel passaggio precedente.
Ora, sia GCC 10.1.0 che Clang 10.0.0 (così come GCC:HEAD e Clang:HEAD) hanno un comportamento misto qui. Se dichiariamo un modello di funzione e successivamente lo definiamo (/re-dichiariamolo) usando una sintassi classica mista del modello di funzione con una sintassi abbreviata del modello di funzione, Clang accetta la maggior parte dei casi (definendo una funzione precedentemente dichiarata) mentre GCC rifiuta tutto (vedi il (tentato ) ri-dichiarazioni come funzioni dichiarate separatamente con successivi errori di ambiguità nella risoluzione dell'overload):
// A1: Clang OK, GCC error
template <typename T>
void a(T);
void a(auto) {}
// B1: Clang OK, GCC error
void b(auto);
template <typename T>
void b(T) {}
// C1: Clang OK, GCC error
template <typename T, typename U>
void c(T, U);
void c(auto, auto) {}
// D1: Clang OK, GCC error
template <typename T, typename U>
void d(T, U);
template <typename T>
void d(T, auto) {}
// E1: Clang error, GCC error
template <typename T>
void e(T, auto);
template <typename T>
void e(auto, T) {}
int main() {
a(0); // Clang OK, GCC error.
b(0); // Clang OK, GCC error.
c(0, '0'); // Clang OK, GCC error.
d(0, '0'); // Clang OK, GCC error.
e(0, '0'); // Clang error, GCC error.
}
Curiosamente, se trasformiamo il modello di funzione in un modello di funzione membro della classe, sia GCC che Clang accettano i casi da A1 a D1 , ma entrambi rifiutano il caso finale E1 sopra:
// A2: OK
struct Sa {
template <typename T>
void a(T);
};
void Sa::a(auto) {}
// B2: OK
struct Sb {
void b(auto);
};
template <typename T>
void Sb::b(T) {}
// C2: OK
struct Sc {
template <typename T, typename U>
void c(T, U);
};
void Sc::c(auto, auto) {}
// D2: OK
struct Sd {
template <typename T, typename U>
void d(T, U);
};
template <typename T>
void Sd::d(T, auto) {}
// E2: Error
struct Se {
template <typename T>
void e(T, auto);
};
template <typename T>
void Se::e(auto, T) {}
con i seguenti messaggi di errore:
GCC
error: no declaration matches 'void Se::e(auto:7, T)' note: candidate is: 'template<class T, class auto:6> void Se::e(T, auto:6)'
Clang
error: out-of-line definition of 'e' does not match any declaration in 'Se'
Ora, non è necessario che il nome di un parametro del modello di tipo sia coerente rispetto alla nuova dichiarazione (o definizione) di un modello di funzione, in quanto nomina semplicemente un segnaposto di tipo generico.
Il messaggio di errore di GCC è particolarmente interessante, suggerendo che i parametri del modello di tipo inventato sono trattati come tipi concreti piuttosto che segnaposto di tipo generico.
Domanda:
- Quali di GCC e Clang sono corretti riguardo ai casi da A1 a D1 (rifiuto e accettazione, rispettivamente)? GCC e Clang hanno ragione a rifiutare il caso E2 di cui sopra? Quale passaggio standard (della bozza di lavoro) li sostiene inequivocabilmente?
Risposte
Questo:
template <typename T> void e(T, auto);
Si traduce in questo:
template<typename T, typename U>
void e(T, U);
Al contrario, questo:
template <typename T> void e(auto, T) {}
si traduce in:
template <typename T, typename U>
void e(U, T) {}
Ricordare che i parametri del modello di funzione abbreviati sono posizionati alla fine dell'elenco dei parametri del modello . Quindi quelli non dichiarano lo stesso modello, a causa dell'inversione dell'ordine dei parametri del modello. Il primo dichiara un modello, il secondo dichiara e definisce un modello diverso .
Non si ottiene un errore di compilazione solo da questo perché anche la seconda definizione è una dichiarazione. Tuttavia, quando si utilizza un membro della classe, le definizioni fuori membro non sono dichiarazioni . Pertanto, devono avere una dichiarazione interna corrispondente. Cosa che non fanno; da qui gli errori.
Come per gli altri, il testo "funzionalmente equivalente (ma non equivalente)" è una notazione non normativa. L'attuale testo normativo che hai citato afferma chiaramente che questi sono "equivalenti", non semplicemente "funzionalmente equivalenti". E poiché il termine "equivalente", per [temp.over.link]/7 , è usato per abbinare dichiarazioni e definizioni, mi sembra che lo standard affermi che i casi da A a D vanno bene.
La cosa strana è che questo testo non normativo è stato introdotto dalla stessa proposta che ha introdotto il testo normativo . Tuttavia, la proposta da cui ha ereditato la ConceptName auto
sintassi sembra chiaro che significhi "equivalente", non "funzionalmente equivalente" .
Quindi, in termini di testo normativo, tutto sembra chiaro. Ma la presenza della contraddizione non normativa suggerisce o un problema editoriale o un effettivo difetto di specifica.
Mentre lo standard stesso è chiaro e normativamente ragionevole in termini di formulazione, sembra che questo non sia ciò che intendevano gli autori dello standard .
P0717 ha introdotto il concetto di "funzionalmente equivalente" come distinto da "equivalente". E quella proposta è stata accettata. Tuttavia, P0717 è stato introdotto all'inizio del processo di adozione di Concepts TS per C++20. In quella proposta, si parlava specificamente di sintassi modello concisa, ed EWG ha votato esplicitamente a favore dell'adozione di una formulazione "funzionalmente equivalente" invece della formulazione "equivalente" di Concepts TS.
Cioè, P0717 chiarisce che il comitato intendeva richiedere agli utenti di utilizzare una sintassi coerente.
Tuttavia, la sintassi concisa del modello da Concepts TS è stata rimossa da C++20 (o meglio, mai veramente aggiunta). Il che significava che qualsiasi formulazione "funzionalmente equivalente" non è mai entrata, dal momento che la funzione non è mai entrata.
Poi è successo P1141, che ha aggiunto la sintassi del modello abbreviata, che copriva gran parte del terreno della sintassi del modello conciso di Concepts TS. Ma, nonostante uno degli autori di P0717 sia un autore di P1141, a quanto pare qualcuno ha commesso un errore nella formulazione e nessuno l'ha colto. Questo spiegherebbe perché il testo non normativo richiami la mancanza di una vera equivalenza: perché quello era proprio l'intento della commissione.
Quindi è molto probabile che si tratti di un errore nel testo normativo.