Program sedang dikompilasi secara berbeda dalam 3 kompiler C ++ utama. Yang mana yang benar?

116

Sebagai tindak lanjut yang menarik (meskipun tidak terlalu penting secara praktis) untuk pertanyaan saya sebelumnya: Mengapa C ++ memungkinkan kita untuk mengapit nama variabel dalam tanda kurung saat mendeklarasikan variabel?

Saya menemukan bahwa menggabungkan deklarasi dalam tanda kurung dengan fitur nama kelas yang disuntikkan dapat menyebabkan hasil yang mengejutkan terkait perilaku kompilator.

Lihat program berikut:

#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. Mengompilasi dengan g ++ 4.9.2 memberi saya kesalahan kompilasi berikut:

    main.cpp:16:10: error: cannot call constructor 'C::C' directly [-fpermissive]
  2. Ini berhasil dikompilasi dengan MSVC2013 / 2015 dan mencetak C (B *)

  3. Ini berhasil dikompilasi dengan clang 3.5 dan cetakan C

Jadi pertanyaan wajibnya adalah mana yang benar? :)

(Saya sangat bergoyang ke arah versi clang dan cara pnidui untuk berhenti mendeklarasikan variabel setelah hanya mengubah tipe dengan secara teknis typedef-nya tampak agak aneh)

Predelnik
sumber
3
C::C y;tidak masuk akal, bukan? Begitu pula C::C (y); pada awalnya saya pikir ini adalah contoh dari Most-Vexing-Parse stackoverflow.com/questions/tagged/most-vexing-parse , tetapi sekarang saya pikir itu hanya perilaku tidak terdefinisi yang berarti ketiga kompiler "benar".
Dale Wilson
4
Dentang # 3 jelas salah, # 2 pnidui terlalu permisif dan # 1 g ++ benar ((saya kira)
8
C::Ctidak menamai jenis, ia menamai suatu fungsi, jadi GCC tepat.
Galik

Jawaban:

91

GCC benar, setidaknya sesuai dengan aturan pencarian C ++ 11. 3.4.3.1 [class.qual] / 2 menetapkan bahwa, jika penentu nama yang disarangkan sama dengan nama kelas, ini merujuk ke konstruktor bukan nama kelas yang dimasukkan. Ini memberi contoh:

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

Sepertinya MSVC salah menafsirkannya sebagai ekspresi cor gaya-fungsi yang membuat sementara Cdengan ysebagai parameter konstruktor; dan Clang salah mengartikannya sebagai deklarasi variabel yang disebut ytipe C.

Mike Seymour
sumber
2
Ya, 3.4.3.1/2 adalah kuncinya. Kerja bagus!
Balapan Ringan di Orbit
Ia mengatakan "Dalam pencarian di mana nama fungsi tidak diabaikan". Tampak bagi saya bahwa dalam contoh yang diberikan, khususnya A::A a;, nama fungsi harus diabaikan - atau tidak?
Columbo
1
Dengan penomoran di N4296, kuncinya adalah 3.4.3.1/2.1: "jika nama yang ditentukan setelah penentu-nama-bersarang, ketika mencari di C, adalah nama-kelas yang disuntikkan dari C [...] nama tersebut dianggap sebagai nama konstruktor kelas C. " Ringkasan Mike agak terlalu disederhanakan - misalnya, typedef dari nama kelas di dalam kelas akan memungkinkan penentu nama bertingkat yang berbeda dari nama kelas untuk tetap merujuk ke nama kelas, jadi masih akan merujuk ke ctor.
Jerry Coffin
2
@Mgetz: Dari pertanyaan: "Ini berhasil dikompilasi dengan MSVC2013 / 2015 dan dicetak C (B *)" .
Balapan Ringan di Orbit
2
Untuk kelengkapan, ini harus menjelaskan apakah bentuknya salah dengan diagnosis yang diperlukan, atau bentuknya buruk tanpa perlu diagnosis. Jika yang terakhir maka semua kompiler "benar".
MM
16

G ++ benar karena memberikan kesalahan. Karena konstruktor tidak dapat dipanggil secara langsung dalam format seperti itu tanpa newoperator. Dan meskipun kode Anda memanggil C::C, ini terlihat seperti panggilan konstruktor. Namun, menurut C ++ 11 standar 3.4.3.1, ini bukan panggilan fungsi legal, atau nama tipe ( lihat jawaban Mike Seymour ).

Dentang salah karena tidak memanggil fungsi yang benar.

MSVC adalah sesuatu yang masuk akal, tetapi tetap saja tidak mengikuti standar.

Kun Ling
sumber
2
Apa yang newdiubah operator?
Neil Kirk
1
@NeilKirk: Banyak, untuk orang-orang yang menganggap itu new B(1,2,3)semacam "panggilan konstruktor langsung" (yang, tentu saja, bukan) sebagai perbedaan dari contoh sementara B(1,2,3)atau deklarasi B b(1,2,3).
Balapan Ringan di Orbit
@LightningRacisinObrit Bagaimana Anda menjelaskan apa new B(1,2,3)itu?
pengguna2030677
1
@ user2030677: Ekspresi baru, menggunakan kata kunci new, nama tipe, dan daftar argumen konstruktor. Ini masih bukan "panggilan konstruktor langsung".
Balapan Ringan di Orbit
"Dentang salah karena ia bahkan tidak memanggil fungsi yang benar.": Saya pikir (karena pernyataan OP tentang tanda kurung dalam deklarasi) yang ditafsirkan Clang C::C (y); sebagai C::C y;, yaitu definisi variabel y tipe C (menggunakan tipe C yang diinjeksi: : C sementara secara keliru mengabaikan spesifikasi bahasa 3.4.1,2 yang semakin gila yang membuat C :: C sebagai konstruktor). Itu bukan kesalahan yang mencolok seperti yang Anda pikirkan, imo.
Peter - Pulihkan Monica