2015-02-26 13 views
5

dlaczego następujący kod się nie kompiluje i kiedy usunę jawne słowo kluczowe przed konstruktorem w klasie A, kompiluje?Wpływ "jawnych" konstruktorów na rozdzielczość przeciążania

Korzystanie z Visual Studio 2013:

enum E { e1_0, e1_1 }; 

template<typename T> 
struct A 
{ 
    A() {} 
    explicit A(unsigned long) {} 
    A(T) {} 
}; 

struct B 
{ 
    B() {} 
    B(E) {} 
}; 


void F(B) {}; 
void F(A<short>) {}; 

void test() 
{ 
    F(e1_0); 
} 

Błąd:

1>------ Build started: Project: exp_construct_test, Configuration: Debug Win32 ------ 
1> exp_construct_test.cpp 
1>e:\exp_construct_test\exp_construct_test.cpp(23): error C2668: 'F' : ambiguous call to overloaded function 
1>   e:\exp_construct_test\exp_construct_test.cpp(19): could be 'void F(A<short>)' 
1>   e:\exp_construct_test\exp_construct_test.cpp(18): or  'void F(B)' 
1>   while trying to match the argument list '(E)' 
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ========== 

Edit: Pobrałem szczęk i skompilowany z dzyń-CL, który zgłasza błąd dla obu przypadków. Jak podkreślono w komentarzach, dwuznaczność jest pomiędzy A<short>(short) i B(E).

Więc być może jest błąd w VC++, że po usunięciu explicit z A(unsigned long), kompilator, niezależnie od zamiaru wybiera B (E), aby podnieść błąd dwuznaczności. Czy ktokolwiek może potwierdzić zachowanie klang jako standardową, a VC++ jako buggy?

dodałem

void G(E) {}; 
void G(short) {}; 

oraz zaproszenie do G takiego:

G(e1_0); 

Które nie zgłosi błąd. Dlaczego tutaj G(E) jest preferowany, a w przypadku kandydatów A<short>::A(short) i B::B(E) są one niejednoznaczne?

End Edit

Dzięki --joja

+2

Jeśli usuniemy słowo 'explicit', to ono również nie zostanie skompilowane, a także nie powinno. Niejednoznaczne wywołanie nie należy do 'A :: A', lecz do' F'. Zauważ, że 'A :: A (T)' nie jest 'jawne', więc wywołanie' F' jest niejednoznaczne. Jeśli * dodasz * an 'explicit' do' A :: A (T) ', to będzie ono rządzić tą konwersją, a kod się skompiluje. – 5gon12eder

+0

Jednoznaczne słowo kluczowe przed konstruktorem zapobiega nieinicjowanemu tworzeniu instancji tego typu za pomocą parametru, który działałby jako argument konstruktora. Kiedy usuniesz "explicite" z konstruktora A, możesz skonstruować A po prostu przekazując 'void F (A )' krótko, czyli co F (e1_0) wydaje się robić – Prismatic

+0

@ 5gon12eder, którego kompilatora używasz? W VS2013 kompiluje się bez "jawnego". Wiem, że niejednoznaczne wywołanie to 'F', ale przyczyną jest konwersja' e1_0' do typu, który akceptuje przeciążenie 'F'. Kiedy 'explicit' jest usuwany, kompilator wybiera' B (E) ', aby wykonać konwersję i wywołuje' F (B) '. Dodałem "explicite", aby upewnić się, że nie używam domyślnie konstruktora, który jest konstruktorem tylko dla wyjątków w moim prawdziwym kodzie. Moim zdaniem użycie 'explicit' powinno jeszcze bardziej poprowadzić kompilator do użycia konwersji B (E) i nie brać pod uwagę promocji' enum E' na 'unsigned long'. – joja

Odpowiedz

3

Spójrzmy na różnych odmianach swojej przykład jedna po drugiej.

  1. Oryginalny przykład wywoływania f(e0).

    enum E {e0, e1}; 
    
    template<typename T> 
    struct A 
    { 
        A(); // (1) 
        explicit A(unsigned long); // (2) 
        A(T); // (3) 
    }; 
    
    struct B 
    { 
        B(); // (4) 
        B(E); // (5) 
    }; 
    
    void f(A<short>); // (6) 
    void f(B); // (7) 
    
    void g(E); // (8) 
    void g(short); // (9) 
    

    Spośród trzech możliwości

    • konwersji e0 do unsigned long utwórz A<short> z niego przez konstruktora (2) i wywołać przeciążeniowe (6)
    • konwersji e0 do short, utworzyć A<hort> z tego przez konstruktora (3) i wywołać przeciążenie (6) i
    • utworzyć B z e0 za pośrednictwem konstruktora (5) i przeciążenie połączenia (7)

    pierwsza opcja nie ma zastosowania, ponieważ (2) jest explicit. Pozostałe dwie obejmują konwersję zdefiniowaną przez użytkownika, która jest uważana za równie dobrą i żadna nie jest brana na korzyść drugiej. Połączenie jest niejednoznaczne, a program źle sformułowany.

  2. Usuńmy z konstruktora explicit i zadzwoń pod numer f(e0).

    template<typename T> 
    struct A 
    { 
        A(); // (1) 
        A(unsigned long); // (2) 
        A(T); // (3) 
    }; 
    
    struct B 
    { 
        B(); // (4) 
        B(E); // (5) 
    }; 
    

    trzech opcji pozostają takie same, ale tym razem, wszystkie trzy są stosowane, a połączenie jest (jeszcze) niejednoznaczne i program źle sformułowane.

  3. Zróbmy obu konstruktorów explicit i zadzwoń f(e0).

    template<typename T> 
    struct A 
    { 
        A(); // (1) 
        explicit A(unsigned long); // (2) 
        explicit A(T); // (3) 
    }; 
    
    struct B 
    { 
        B(); // (4) 
        B(E); // (5) 
    }; 
    

    To sprawia, że ​​niemożliwe jest niejawnie skonstruować A<short> a połączenie jednoznacznie odnosi się do nadmiaru (5).

  4. Zróbmy też konstruktora Bexplicit i zadzwoń pod numer f(e0).

    template<typename T> 
    struct A 
    { 
        A(); // (1) 
        explicit A(unsigned long); // (2) 
        explicit A(T); // (3) 
    }; 
    
    struct B 
    { 
        B(); // (4) 
        explicit B(E); // (5) 
    }; 
    

    Tym razem żaden z trzech ścieżek konwersji ma zastosowanie, ponieważ każdy z nich pójdzie za pośrednictwem konstruktora explicit. Nie ma przeciążenia f, które ma zastosowanie, a program jest źle sformułowany.

  5. Zadzwoń pod g(e0).

    Mamy dwie możliwości tutaj:

    • połączeń przeciążenia (8) bez jakiejkolwiek konwersji lub
    • konwersji e0 do short i nazywają przeciążenie (9).

    Spośród tych dwóch opcji pierwsza opcja jest wyraźnie korzystna, ponieważ nie wiąże się z konwersją. Połączenie jest jednoznaczne. (Nawet jeśli konstruktor (5) nie jest explicit.)

Zauważ, że domyślny konstruktorzy (1) i (4) faktycznie nie przyczyniają się coś do tej dyskusji. Testy z GCC 4.9.1, wszystkie pięć przykładów zachowuje się zgodnie z oczekiwaniami.

+0

Dzięki, @ 5gon12eder, za tę odpowiedź. Mam kilka uwag: - Punkt 2 .: z konstruktorem (2) ** nie ** oznaczonym jawnie, ani w VisualC, ani w klangie Otrzymałem trzy możliwe przeciążenia zgłoszone. - Punkt 1 .: Przy (2) jawnym, wywołującym f (e0) Oczekiwałem (5) do wywołania, ponieważ użyta jest tylko zdefiniowana przez użytkownika konwersja konstruktora, E -> B (E), dla (3) jest potrzebna jedna więcej kroków: E -> krótki -> A (krótki). - A więc, czy potwierdzasz, Visual C++ jest wadliwy, gdy kompiluje przykład punktu 2. bez podnoszenia jakiegokolwiek komunikatu o błędzie i cichego wywoływania B (E)? – joja

+0

@joja ** punkt reklamy 1: ** To może wydawać się intuicyjne, ale o ile mogę powiedzieć, nie ma reguły w standardzie, która plasuje jedną z tych sekwencji konwersji lepiej niż druga, więc wywołanie jest niejednoznaczne. ** punkt reklamowy 2: ** Nie będę się spieszyć, twierdząc, że kompilator, do którego nie mam dostępu, ma błąd, ale jestem raczej pewien, że moje rozumowanie i zachowanie GCC jest tutaj poprawne. Być może bardziej zaawansowany prawnik językowy przyjdzie i skomentuje z większym autorytetem w tej kwestii. – 5gon12eder

Powiązane problemy