2012-04-02 10 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); 
} 

co jest powodem, dla którego dzwoni d.f jest w porządku, ale c.f dajeNiejednoznaczne gdy dwa superklasy posiada funkcję składową o tej samej nazwie, ale różnych podpisów

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

Dobre pytanie. Sądzę, że zwykłe reguły rozwiązywania przeciążenia nie mają zastosowania w 'C' (ponieważ zwykle nie szablony są preferowane do szablonów, jeśli istnieje dopasowanie, stąd zachowanie z' D'). –

+3

Dodałbym do pytania OP: "jakakolwiek standardowa reguła C++ wymusza takie zachowanie: jakie jest uzasadnienie tej reguły?" – Vlad

+2

@ Vlad Myślę, że zachowanie jest całkiem rozsądne. Nie spowodowanie tutaj błędu może prowadzić do wielu nieprzyjemnych błędów. Dobre pytanie. – enobayram

Odpowiedz

11

Pierwsza część to ze względu na członka wyszukiwanie nazwy, dlatego nie działa.

chciałbym skierować do: 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.

Teraz, po kilku z funkcji szablonów.

Zgodnie 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.

A jeśli dalej czytać 13.3.3/1 Best viable function

F1 jest uważany za lepiej funkcja, jeżeli:

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

Dlatego następujące kompiluje snippet i uruchamia funkcję bez szablonu bez błędu:

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

, ale dlaczego kompilator widzi tylko 'A :: f', ale nie' B :: f'? Gdzie jest różnica w przestrzeni nazw? – Vlad

+0

Czy możesz to potwierdzić cytatem ze standardu? –

+0

Obecnie pracuję przy założeniu, że ignoruje on wszystkich innych członków bazy, gdy tylko znajdzie jeden. Byłoby interesujące wiedzieć, czy możesz kontrolować, które jest wywoływane przez zmianę kolejności dziedziczenia, np. 'struct C: public B, public A' –

0

Kompilator nie wie, którą metodę wywołać z klasy C, ponieważ szablonowa metoda zostanie przetransmitowana w void f (int) w przypadku typu int, więc masz dwie metody o tej samej nazwie i tych samych argumentach, ale członkowie różnych klas rodziców.

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

lub

void f(int) 

spróbuj tego:

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

czy to w klasie A:

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

Pytanie OP nie brzmi "jak", to jest "dlaczego"? – Vlad

+0

Odpowiedź na pytanie: dlaczego kompilator nie wie, którą metodę wywołać z klasy C – AlexTheo

+2

Ok ... dlaczego wie dla klasy D? –

1

wierzę, kompilator preferuje A::f (funkcja bez szablonu) ponad B::f bez powodu.
Wydaje się, że to błąd implementacyjny kompilatora więcej niż szczegóły zależne od implementacji.

Jeśli dodać następujący wiersz, a następnie compilation goes fine i prawidłowa funkcja B::f<> wybrano:

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

[Zabawne jest to, że aż ::f nie są doprowadzone do zakresu C, są one traktowane jako funkcje obcych .]

+0

Co ciekawe, jeśli skomentujemy 'używając A :: f;' A :: f jest ukryte przez B :: f, podczas gdy przy obu deklaracjach 'using'' cf (3) 'i' cf (3) 'poprawnie wywołać odpowiednie funkcje. – jrok

+1

Z wyjątkiem tego, że kompilator nie preferuje 'A :: f' - bierze pod uwagę oba i nie działa z powodu niejasności. –

+0

@MikeSeymour, to pytanie brzmi, dlaczego kompilator nie ma takiej niejednoznaczności w przypadku 'D :: f'? W tym przypadku wydaje mi się, że jest to dla mnie korzystne. – iammilind

0

Rozważmy prostszy przykład:

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); 
} 

W tym przykładzie kompiluje się tak samo, jak D, ale nie ma wartości C.
Jeśli klasa jest wyprowadzona, mechanizm wyszukiwania członków zachowuje się inaczej. Sprawdza każdą klasę bazową i scala je: W przypadku C; Każda klasa bazowa odpowiada wyszukiwaniu (A :: f (int) i B :: f (float)). Po ich scaleniu C decyduje, że są niejednoznaczne.

Dla klasy przypadku D: int wersja jest wybrany zamiast float ponieważ parametr jest liczbą całkowitą.

+0

Dziękuję, to potwierdza tezę, że gdy nie znajdzie metody, przechodzi do superklatek, generuje wszystkie możliwości i narzeka, jeśli istnieje wiele możliwości. jako @jammilind i jrok wskazują, przy użyciu tego generuje je wcześniej i sprawdza najlepiej pasujące jeden –

+0

To tylko obserwacja. Bez wspierającego cytatu ze standardu jest bezużyteczny. Może to być błąd kompilatora. –

+0

@LuchianGrigore tak, dlatego czekam w przełączaniu zaakceptowanej odpowiedzi zaznacz –

0

Co dzieje się prawdopodobnie jest to, że konkretyzacja szablonu dzieje się oddzielnie dla klasy A i B, kończąc w ten sposób w dwóch void f(int) funkcji.

To nie zdarza się w D ponieważ kompilator wie o funkcji void f(int) jako specjalizacji i dlatego nie specjalizują T dla int.

Powiązane problemy