2016-04-19 36 views
8

Nel seguente codice C ad esempio ++, GCC 6 e Clang 3.8 in disaccordo su ciò che il comportamento corretto è:funzioni friend template di oggetti e spazi dei nomi

Questo esempio inventato "funziona" - come nella funzione test() ritorna o.p in GCC. In clang, chiama il (undefined) Funzione get<int, int, float, double>:

template<typename ...Args> 
class obj { 
bool p = false; 

template<typename T, typename... Args2> 
friend T get(const obj<Args2...> &o) { return o.p; } 
}; 

template<typename T, typename... Args> 
T get(const obj<Args...> &o); 


bool test(const obj<int, float, double> &a) { 
return get<int>(a); 
} 

Mettere lo stesso codice in uno spazio dei nomi provoca GCC per fare la stessa cosa clang fa.

namespace ns { 

template<typename ...Args> 
class obj { 
bool p = false; 

template<typename T, typename... Args2> 
friend T get(const obj<Args2...> &o) { return o.p; } 
}; 

template<typename T, typename... Args> 
T get(const obj<Args...> &o); 

} 

bool test(const ns::obj<int, float, double> &a) { 
return ns::get<int>(a); 
} 

https://godbolt.org/g/sWrXQO e https://godbolt.org/g/9tIXwe

Quale compilatore è "corretto" ed è là in modo generale per definire un membro amico funzione template in linea, senza dover dichiarare e quindi definire separatamente. Cioè, le cose come:

struct Foo { 
friend bool bar() { return true; } // declares *and* defines a free function bar 
template<typename T> T bar2() { return true; } // doesn't work! 
}; 
+0

Perché dichiarando 'template T get (const obj &o); classe' fuori – Jarod42

+0

Perché non funziona se non si ... provare per credere:? Https://godbolt.org/g/peSscU –

+0

Sì, 'T' non è deducibile, quindi devi fornire' ', quindi ADL non funziona (dato che non ci sono template' get' in ambito globale):/Puoi dichiarare un modello fittizio ottenere (con corrispondenza non corretta) per riattivare ADL: [Demo] (http://coliru.stacked-crooked.com/a/e40aaf90de712943). – Jarod42

risposta

5

Ci sono due questioni irrisolte riguardanti modelli friend funzione definita in modelli di classe: 1545 e 2174. Il primo mette in discussione la misura in cui è valido e il secondo riguarda le violazioni odr che possono sorgere in base alle istanze effettive di quei modelli di funzione. Non sono sicuro di quale compilatore sia corretto (avendo precedentemente creduto che entrambi fossero sbagliati), ma potrebbe semplicemente essere sottovalutato o scarsamente specificato nello standard quale sia il comportamento corretto in questa situazione.

Il codice dovrebbe idealmente compilare (in attesa di risoluzione dei problemi):

template<typename ...Args> 
class obj { 
bool p = false; 

template<typename T, typename... Args2> 
friend T get(const obj<Args2...> &o) { return o.p; } 
}; 

template<typename T, typename... Args> 
T get(const obj<Args...> &o); 

La dichiarazione friend prima dichiara get, quindi questo crea un nuovo membro dello spazio dei nomi più interno: ::get. La dichiarazione esterna semplicemente ridisegna la stessa funzione, quindi c'è davvero solo quella ::get. Da [temp.over.link]:

Due espressioni che coinvolgono parametri del modello sono considerate equivalenti se due definizioni di funzioni che contengono le espressioni avrebbe soddisfatto la regola una definizione (3.2), tranne che i token utilizzati per il nome del I parametri del modello possono variare a condizione che un token utilizzato per denominare un parametro modello in un'espressione sia sostituito da un altro token che denomina lo stesso parametro del modello nell'altra espressione. Per determinare se due nomi dipendenti (14.6.2) sono equivalenti, viene considerato solo il nome stesso, non il risultato della ricerca del nome nel contesto del modello.

Usando diversi nomi dei parametri del modello (Args... vs Args2...) va bene - questa seconda dichiarazione del modello di funzione ::get è valido e permette per la ricerca per trovarlo.

Questo ci porta a:

bool test(const obj<int, float, double> &a) { 
#ifdef UNQUAL 
    return get<int>(a);  // unqualified 
#else 
    return ::get<int>(a); // qualified 
#endif 
} 

Entrambi questi dovrebbe lavoro - da quando abbiamo dichiarato nuovamente la funzione di essere in ambito spazio dei nomi, non hanno nemmeno bisogno di preoccuparsi di ADL.Entrambe le chiamate dovrebbero trovare ::get<int>(obj<int, float, double>), che è un friend, quindi il codice dovrebbe essere compilato e collegato. gcc consente la chiamata non qualificata ma non la chiamata qualificata, clang non consente né.

Potremmo eludere entrambe le questioni CWG interamente semplicemente non definizione del modello di funzione all'interno della classe:

template<typename ...Args> 
class obj { 
bool p = false; 

template<typename T, typename... Args2> 
friend T get(const obj<Args2...> &o); 
}; 

template<typename T, typename... Args> 
T get(const obj<Args...> &o) { return o.p; } 

Con questa formulazione, entrambe le compilazioni permettono sia invocazione qualificati e non qualificati di get.


Se riscrivere il codice in modo tale che obj è una classe e non un modello di classe, ma tutto il resto è uguale:

class obj { 
    bool p = false; 

    template <class T> 
    friend T get(const obj& o) { return o.p; } 
}; 

template <class T> T get(const obj&); 

bool test(const obj& a) { 
#ifdef UNQUAL 
    return get<int>(a); 
#else 
    return ::get<int>(a); 
#endif 
} 

Entrambi i compilatori consentono entrambe le invocazioni. Ma non c'è alcuna differenza di cui io sia a conoscenza nelle regole tra obj come una normale classe e un modello di classe.

+0

Grazie per la risposta completa. Apprezzo la soluzione alternativa (di definire il get esternamente) - e questo è ciò che sto già facendo nel mio codice. Tuttavia la disparità tra la funzione non templata può essere definita in linea (e comunemente fatta con, ad esempio, l'operatore amico <<), e tuttavia non appena la funzione è essa stessa templata, dovendo separare la sua dichiarazione e definizione ... è semplicemente confuso. Ma ... è C++ :) –

+2

@MattG I requisiti del modello esterno possono essere meglio spiegati in [questo commento] (http://stackoverflow.com/questions/2953684/why-doesnt-adl-find-function-templates#comment29830195_2953783). Inoltre, non sono sicuro al 100% che il mio ragionamento sia corretto riguardo ai compilatori. Sperando che qualcun altro passi qui ad un certo punto. – Barry

+1

CWG2174 e CWG1545 potrebbero essere rilevanti. –