Program jest kompilowany inaczej w 3 głównych kompilatorach C ++. Który jest poprawny?


116

Jako ciekawa kontynuacja (choć nie ma to dużego znaczenia praktycznego) mojego poprzedniego pytania: Dlaczego C ++ pozwala nam umieszczać nazwę zmiennej w nawiasach podczas deklarowania zmiennej?

Dowiedziałem się, że połączenie deklaracji w nawiasach z właściwością wstrzykniętej nazwy klasy może prowadzić do zaskakujących wyników w zachowaniu kompilatora.

Spójrz na następujący program:

#include <iostream>
struct B
{
};

struct C
{
  C (){ std::cout << "C" << '\n'; }
  C (B *) { std::cout << "C (B *)" << '\n';}
};

B *y = nullptr;
int main()
{
  C::C (y);
}
  1. Kompilacja z g ++ 4.9.2 daje następujący błąd kompilacji:

    main.cpp:16:10: error: cannot call constructor 'C::C' directly [-fpermissive]
  2. Kompiluje się pomyślnie z MSVC2013 / 2015 i drukuje C (B *)

  3. Kompiluje się pomyślnie z clang 3.5 i drukuje C

Tak więc obowiązkowe pytanie brzmi, który z nich jest właściwy? :)

(Silnie kołysałem się w kierunku wersji clang i sposób, w jaki msvc przestał deklarować zmienną po zmianie typu, a technicznie jego typedef wydaje się trochę dziwny)


3
C::C y;nie ma sensu, prawda? Ani też. C::C (y); Na początku myślałem, że to wystąpienie Most-Vexing-Parse stackoverflow.com/questions/tagged/most-vexing-parse , ale teraz myślę, że jest to po prostu niezdefiniowane zachowanie, co oznacza, że ​​wszystkie trzy kompilatory mają „rację”.
Dale Wilson

4
# 3 clang jest zdecydowanie nie tak, # 2 msvc jest zbyt pobłażliwe, a # 1 g ++ ma rację ((chyba)

8
C::Cnie nazywa typu, nazywa funkcję, więc GCC ma rację imo.
Galik


Odpowiedzi:


91

GCC jest poprawne, przynajmniej zgodnie z regułami wyszukiwania w C ++ 11. 3.4.3.1 [class.qual] / 2 określa, że ​​jeśli zagnieżdżony specyfikator nazwy jest taki sam jak nazwa klasy, to odwołuje się do konstruktora, a nie do wstrzykniętej nazwy klasy. Podaje przykłady:

B::A ba;           // object of type A
A::A a;            // error, A::A is not a type name
struct A::A a2;    // object of type A

Wygląda na to, że MSVC błędnie interpretuje je jako wyrażenie rzutowania w stylu funkcji, tworząc tymczasowe Cz yparametrem konstruktora; a Clang błędnie interpretuje to jako deklarację zmiennej o nazwie ytyp C.


2
Tak, kluczem jest 3.4.3.1/2. Dobra robota!
Wyścigi lekkości na orbicie

Mówi „W wyszukiwaniu, w którym nazwy funkcji nie są ignorowane”. Wydaje mi się, że w podanych przykładach w szczególności A::A a;należy zignorować nazwy funkcji - czy nie?
Columbo

1
Idąc za numeracją w N4296, klucz to tak naprawdę 3.4.3.1/2.1: "jeśli nazwa określona po zagnieżdżonym specyfikatorze nazwy, podczas wyszukiwania w C, jest wstrzykniętą nazwą klasy C [...] zamiast tego uważa się, że nazwa określa konstruktora klasy C. " Podsumowanie Mike'a jest jednak trochę zbyt uproszczone - na przykład zdefiniowanie typu nazwy klasy wewnątrz klasy pozwoliłoby zagnieżdżonemu specyfikatorowi nazwy, różnemu od nazwy klasy, nadal odwoływać się do nazwy klasy, więc nadal odnosiłby się do ctor.
Jerry Coffin

2
@Mgetz: Z pytania: „Kompiluje się pomyślnie z MSVC2013 / 2015 i drukuje C (B *) .
Wyścigi lekkości na orbicie

2
Dla kompletności powinno to wyjaśnić, czy jest źle sformułowany i nie wymaga diagnostyki, czy też jest źle sformułowany i nie jest wymagany. Jeśli to drugie, wszystkie kompilatory mają rację.
MM,

16

G ++ jest poprawne, ponieważ daje błąd. Ponieważ konstruktora nie można wywołać bezpośrednio w takim formacie bez newoperatora. I chociaż Twój kod wywołuje C::C, wygląda to jak wywołanie konstruktora. Jednak zgodnie ze standardem 3.4.3.1 języka C ++ 11 nie jest to legalne wywołanie funkcji ani nazwa typu ( patrz odpowiedź Mike'a Seymoura ).

Clang jest błędny, ponieważ nawet nie wywołuje prawidłowej funkcji.

MSVC to coś rozsądnego, ale nadal nie spełnia standardów.


2
Co zmienia newoperator?
Neil Kirk

1
@NeilKirk: Sporo, dla ludzi, którzy myślą, że new B(1,2,3)jest to coś w rodzaju „bezpośredniego wywołania konstruktora” (którym oczywiście nie jest) w odróżnieniu od tymczasowej instancji B(1,2,3)lub deklaracji B b(1,2,3).
Wyścigi lekkości na orbicie

@LightningRacisinObrit Jak opisałbyś, co new B(1,2,3)jest?
user2030677

1
@ user2030677: nowe wyrażenie zawierające słowo kluczowe new, nazwę typu i listę argumentów konstruktora. To wciąż nie jest „bezpośrednie wywołanie konstruktora”.
Wyścigi lekkości na orbicie

"Clang jest błędny, ponieważ nawet nie wywołuje prawidłowej funkcji.": Myślę (ponieważ uwaga OP dotycząca nawiasów w deklaracjach) Clang interpretuje C::C (y); jako C::C y;, tj. Definicję zmiennej y typu C (używając wstrzykniętego typu C: : C, podczas gdy błędnie ignoruje coraz bardziej szaloną specyfikację języka 3.4.1,2, co sprawia, że ​​C :: C jest konstruktorem). To niezbyt rażący błąd, jak ci się wydaje, imo.
Peter - Przywróć Monikę
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.