2012-04-02 12 views
27
struct A { 
    void f(int x) {} 
}; 

struct B { 
    template<typename T> void f(T x) {} 
}; 

struct C : public A, public B {}; 

struct D { 
    void f(int x){} 
    template<typename T> void f(T x) {} 
}; 


int main(int argc, char **argv) { 
    C c; 
    c.f<int>(3); 
    D d; 
    d.f<int>(3); 
} 

Qual è la ragione per la quale chiamare d.f va bene, ma c.fambiguo quando due superclassi hanno una funzione di membro con lo stesso nome, ma firme diverse

error: request for member ‘f’ is ambiguous 
error: candidates are: template<class T> void B::f(T) 
error:     void A::f(int) 
+0

Buona domanda. Immagino che le solite regole di risoluzione da sovraccarico non si applichino in 'C' (dato che normalmente i template non sono preferiti ai template se c'è una corrispondenza, da qui il comportamento con' D'). –

+3

Vorrei aggiungere alla domanda dell'OP: "qualsiasi regola standard C++ impone un tale comportamento: qual è la logica alla base di questa regola?" – Vlad

+2

@Vlad Penso che il comportamento sia abbastanza ragionevole. Non causare qui un errore potrebbe aprire la strada a molti bug sgradevoli. Buona domanda però. – enobayram

risposta

11

La prima parte è dovuto al membro ricerca del nome, ecco perché fallisce.

mi riferisco a: 10.2/2 Member name lookup

The following steps define the result of name lookup in a class scope, C. First, every declaration for the name in the class and in each of its base class sub-objects is considered. A member name f in one sub-object B hides a member name f in a sub-object A if A is a base class sub-object of B. Any declarations that are so hidden are eliminated from consideration. Each of these declarations that was introduced by a using-declaration is considered to be from each sub-object of C that is of the type containing the declaration designated by the using-declaration.

If the resulting set of declarations are not all from sub-objects of the same type, or the set has a nonstatic member and includes members from distinct sub-objects, there is an ambiguity and the program is ill-formed. Otherwise that set is the result of the lookup.

Ora, per la materia con funzioni template.

Come per 13.3.1/7 Candidate functions and argument list

In each case where a candidate is a function template, candidate function template specializations are generated using template argument deduction (14.8.3, 14.8.2). Those candidates are then handled as candidate functions in the usual way. A given name can refer to one or more function templates and also to a set of overloaded non-template functions. In such a case, the candidate functions generated from each function template are combined with the set of non-template candidate functions.

E se si continua a leggere 13.3.3/1 Best viable function

F1 è considerato un meglio funzione, se:

F1 is a non-template function and F2 is a function template specialization

Ecco perché i seguenti compilazioni frammento ed esegue la funzione senza modello senza errori:

D c; 
c.f(1); 
+0

ma perché il compilatore vede solo 'A :: f', ma non' B :: f'? Dov'è la differenza di namespace? – Vlad

+0

È possibile eseguire il backup con una citazione dallo standard? –

+0

Attualmente sto lavorando partendo dal presupposto che ignora tutti gli altri membri di base, non appena ne trova uno. Sarebbe interessante sapere se è possibile controllare quale viene invocato cambiando l'ordine di ereditarietà, ad es. 'struct C: public B, public A' –

0

Un compilatore non sa quale metodo chiamare dalla classe C perché il metodo di modello verrà transormalato in void f (int) in caso di tipo int in modo da avere due metodi con lo stesso nome e gli stessi argomenti ma membri di diverse classi genitore.

template<typename T> void f(T x) {} 

o

void f(int) 

provare questo:

c.B::f<int>(3); 

o questo per la classe A:

c.A::f(3); 
+1

La domanda dell'OP non è _how_, è _why_? – Vlad

+0

La risposta per un perché è: un compilatore non sa quale metodo chiamare dalla classe C – AlexTheo

+2

Ok ... perché lo sa per la classe D? –

1

Credo che il compilatore preferisce A::f (funzione non-modello) oltre B::f senza motivo.
Questo sembra essere un bug di implementazione del compilatore più che un dettaglio dipendente dall'implementazione.

Se si aggiunge seguente riga, quindi compilation goes fine ed è selezionata la funzione corretta B::f<>:

struct C : public A, public B { 
    using A::f; // optional 
    using B::f; 
}; 

[parte divertente è che fino a quando il ::f non sono portati nel campo di applicazione della C, sono trattati come funzioni aliene .]

+0

Iteresting still, se commentiamo 'usando A :: f;' A :: f è nascosto da B :: f, mentre con entrambe le dichiarazioni 'using' cf cf. (3)' e 'cf (3)' correttamente chiamare le funzioni corrispondenti. – jrok

+1

Tranne che il compilatore non preferisce 'A :: f' - considera entrambi e fallisce a causa dell'ambiguità. –

+0

@ MikeSeymour, quindi la domanda è: perché il compilatore non ha tale ambiguità nel caso di 'D :: f'? In questo caso, sembra essere un caso di preferenza per me. – iammilind

0

Si consideri l'esempio più semplice:

struct A{ 
void f(int x){} 
}; 

struct B{ 
void f(float t){} 
}; 


struct C:public A,public B{ 
}; 

struct D{ 
void f(float n){} 
void f(int n){} 
}; 


int main(){ 
C c; 
c.f(3); 

D d; 
d.f(3); 
} 

In questo esempio, uguale al tuo, D compila ma non lo è C.
Se una classe è derivata, il meccanismo di ricerca dei membri si comporta in modo diverso. Controlla ogni classe di base e le unisce: nel caso di C; Ogni classe base corrisponde alla ricerca (A :: f (int) e B :: f (float)). Dopo averli uniti, C decide che sono ambigui.

Per la classe case D: int versione è selezionato invece di float perché parametro è un numero intero.

+0

Grazie, questo supporta la tesi che quando non trova il metodo, sale alle superclassi, genera tutte le possibilità e si lamenta se ci sono più scelte. come @jammilind e jrok sottolineano, con l'utilizzo li genera in anticipo, e controlla il miglior abbinamento –

+0

Questa è solo un'osservazione. Senza una citazione di supporto dallo standard, è inutile. Potrebbe essere un bug del compilatore. –

+0

@LuchianGrigore sì, questo è il motivo per cui sto aspettando di attivare la risposta accettata tick –

0

Ciò che probabilmente sta accadendo è che l'istanziazione del modello sta avvenendo separatamente per la classe A e B, terminando quindi con due funzioni void f(int).

Questo non accade in quanto non vi D il compilatore conosce sulla funzione void f(int) come specializzazione e quindi non specializzarsi T per int.

Problemi correlati