2016-01-09 17 views
15

Si consideri il seguente codice:Template dipendente typename

struct bar 
{ 
    template <typename U> 
    void fun0() const {} 
}; 

template <typename T> 
struct foo 
{ 
    void 
    fun1(const bar& d) 
    { 
    // (1) KO 
    fun2(d).fun0<int>(); 
    // (2) OK 
    fun2(d).template fun0<int>(); 
    // (3) OK   
    d.fun0<int>(); 
    } 

    bar 
    fun2(const bar& d) 
    { 
    return d; 
    } 
}; 

Lines (2) e (3) la compilazione, ma (1) fallisce con:

error: use 'template' keyword to treat 'fun0' as a dependent template name 
    fun2(d).fun0<int>(); 

      ^
      template 

(Come previsto, se foo c'è più una struct template, (1) compila pure)

Perché bar::fun0 un nome di modello dipendente qui? bar non dipende dal parametro modello T di foo.

Edit:

Chiaramente, bar::fun2 è responsabile per l'ambiguità che il .template accordo con. Per esempio, aggiungiamo i 2 seguenti funzioni liberi:

bar 
fun3(const bar& d) 
{ 
    return d; 
} 

template <typename T> 
T 
fun4(const T& d) 
{ 
    return d; 
} 

fun3(d).fun0<int>() e fun4(d).fun0<int>()) compilare anche nel contesto di foo::fun1. Quindi l'ambiguità è causata dal parametro template di foo.

Perché fun2(d).fun0<int>() non è stato analizzato come una chiamata a un modello di funzione membro?

+0

Perché potresti specializzare 'fun2' per restituire qualcosa di diverso da' bar'. –

+1

@AlanStokes In che modo specializzeresti 'fun2' in un modo che permette di cambiare il tipo di ritorno, mantenendo comunque' fun1' in giro? – hvd

+0

@AlanStokes È possibile specializzare 'fun2' solo per restituire un tipo covariante (quindi deve essere derivato da' bar'), quindi è garantito che il tipo covariante abbia la stessa funzionalità (cioè deve avere un 'modello void fun0 () funzione const'). Quindi restituirà sempre un tipo che è in realtà una 'barra' – texasflood

risposta

4

Penso che questo dipenda dalle regole nella sezione 14.6.2 dello standard. Fondamentalmente, penso che le regole siano errate nel richiedere di usare template e typename - una volta che si forma un'espressione che potrebbe essere potenzialmente dipendente, a volte sarà dichiarata tale in base alle regole, anche se un umano può chiaramente vedi che non è in realtà dipendente. Quindi, v'è una regola generale 14.6.2.2.1 che afferma

eccezione di quanto descritto di seguito, l'espressione è di tipo dipendente eventuale subespressione è di tipo-dipendente.

Quello che penso sta accadendo è che l'espressione fun2(d) è dichiarato di essere a seconda del tipo secondo le regole, anche se che tipo è, infatti, lo stesso in ogni istanza di questo (primario) modello, come si e posso vedere - ecco perché è sorprendente che sia richiesto template.

Sotto 14.6.2.1.4 in C++ 11 (o C++ 14) standard

Un nome è un membro dipendente dalla corrente istanziazione se è un membro della istanza corrente che , quando guardato in alto, si riferisce ad almeno un membro di una classe che è l'istanza corrente.

Ciò significa che, il nome è un nome fun2dipendente, anche se fun2 non si riferisce a T o qualsiasi cosa che dipende esplicitamente T.

Così, quando si considera l'espressione che ha dato fun2(d).fun0<int>(), e considerano il "accesso sottoespressione membro", vale a dire, fun2(d).fun0<int> - intuitivamente vogliamo dire che questo non dipende, come fun2(d) è sempre di tipo bar e fun0<int> non dipendono neanche da T. C'è regola 14.6.2.2.5 che afferma

Un'espressione accesso membro della classe (5.2.5) è dipendente dal tipo se l'espressione si riferisce ad un membro della corrente istanziazione e il tipo dell'organo riferimento dipende, o espressione di accesso membro della classe fa riferimento a un membro di una specializzazione sconosciuta.

Nessuna di queste condizioni si applica letteralmente, poiché bar non è lo stesso tipo del istanziazione corrente (foo<T>), e nessuno dei due è fun0<int> un membro di una specializzazione sconosciuta del modello foo. Questo è il motivo per cui l'espressione d.fun0<int>() è ben formata.

Tuttavia, nota chiaramente che questa regola 14.6.2.2.5 viene dopo 14.6.2.2.1 che imposta il significato di tutta questa sezione:

eccezione di quanto descritto di seguito, l'espressione è di tipo dipendente eventuale subespressione è di tipo-dipendente.

Quindi se qualsiasi sottoespressione, come ad esempio fun2, è formalmente "a seconda del tipo", che avvelena l'intera espressione, e fa sì che regole simili da applicare come se stavamo cercando fino membri di parametri di modello o specializzazioni sconosciuti, ecc . ecc

in particolare, la condizione type-dependent significa che è necessario il prefisso template, a causa della regola 14.2.4:

Quando il nome di una specializzazione template membro appare dopo . o -> in un postfix-expression o after a nested-specificatore di nome in un id qualificato e l'espressione dell'oggetto dell'espressione postfix è di tipo dipendente o lo identificatore nome-nidificato nell'ID qualificato si riferisce a un tipo dipendente, ma il nome non è un membro di l'istanza corrente (14.6.2.1), il nome del modello membro deve essere preceduto dal modello di parola chiave. In caso contrario, si assume che il nome indichi un modello diverso da modello.

Esempio:

struct X { 
    template<std::size_t> X* alloc(); 
    template<std::size_t> static X* adjust(); 
}; 
template<class T> void f(T* p) { 
    T* p1 = p->alloc<200>();   // ill-formed: < means less than 
    T* p2 = p->template alloc<200>(); // OK: < starts template argument list 
    T::adjust<100>();     // ill-formed: < means less than 
    T::template adjust<100>();  // OK: < starts template argument list 
} 

- esempio fine]

Ora molto concretamente:

  1. Con [14.2.4], nell'espressione postfissa fun2(d). , se l'espressione oggetto fun2(d) è dipendente dal tipo, quindi è necessario utilizzare il prefisso template per chiamare il modello membro.

  2. Sotto [14.6.2.2.1], se fun2 è dipendente dal tipo allora che le forze fun2(d) essere anche.

  3. E sotto [14.6.2.1.4], dal momento che fun2 quando alzò gli occhi si riferisce ad un membro del template foo di classe, che da sola è sufficiente per renderlo un membro dipendente l'istanza corrente.

io non hanno una tutto chiaro argomento qualsiasi delle regole che se un nome si riferisce ad un membro dipendente dalla corrente istanziazione, allora ciò implica che l'espressione corrispondente al nome è type-dependent. .. e ho cercato un paio di volte adesso.

Tuttavia, questo punto di vista è sostenuto in @Johannes Schaub - litb highly up-voted (but simplified, and non-normative) exposition of the rules:

nomi dipendenti

Lo standard è un po 'poco chiaro su ciò che è esattamente un nome dipendente. In una lettura semplice (si sa, il principio della meno sorpresa), tutto ciò che definisce come un nome dipendente è il caso speciale per i nomi delle funzioni di seguito. Ma dal momento che T: x ha anche bisogno di essere guardato nel contesto dell'istanziazione, deve anche essere un nome dipendente (fortunatamente, a metà del C++ 14 il comitato ha iniziato a esaminare come risolvere questa definizione confusa) .

Per evitare questo problema, ho fatto ricorso a una semplice interpretazione del testo standard. Di tutti i costrutti che denotano tipi o espressioni dipendenti, un sottoinsieme di essi rappresenta nomi. Questi nomi sono quindi "nomi dipendenti". Un nome può assumere diverse forme: lo standard dice:

Un nome è un uso di un identificatore (2.11), ID funzione dell'operatore (13.5), ID funzione di conversione (12.3.2) o modello- id (14.2) che denota un'entità o un'etichetta (6.6.4, 6.1)

Un identificatore è solo una semplice sequenza di caratteri/cifre, mentre i due successivi sono l'operatore + e la forma del tipo di operatore. L'ultima forma è nome-modello. Tutti questi sono i nomi, e da un uso convenzionale nello Standard, un nome possono anche includere qualificazioni che dicono quello spazio dei nomi o classe di un nome deve essere guardato in.

sarebbe grande per avere una spiegazione più concreta di tale ultima parte.

+0

Grazie per questa bella risposta. Quindi sembra che tutto si riduca a una definizione "troppo ampia" di nomi dipendenti nello standard C++? –

+0

Certo, questo è un modo per guardarlo. Che si tratti di un "difetto" è un problema a parte: il fatto è che la grammatica del C++ è ambigua e la scrittura di un parser performante e corretto è difficile. Richiedere 'template' in casi come questo può consentire la scrittura di un parser che è più veloce e utilizza meno memoria. Più tardi il compilatore conosce il tipo di 'fun2 (d)', ma in fase di parser, sta solo cercando di capire se in 'fun0 ' il '<' è un operatore di confronto o l'inizio di un elenco di parametri template - nel momento in cui 'template' e' typename' sono rilevanti, sa a malapena qualcosa del tuo programma. –

Problemi correlati