Terjadi kesalahan saat menggunakan inisialisasi di kelas dari anggota data non-statis dan konstruktor kelas bertingkat

90

Kode berikut cukup sepele dan saya berharap itu harus dikompilasi dengan baik.

struct A
{
    struct B
    {
        int i = 0;
    };

    B b;

    A(const B& _b = B())
        : b(_b)
    {}
};

Saya telah menguji kode ini dengan g ++ versi 4.7.2, 4.8.1, clang ++ 3.2 dan 3.3. Terlepas dari kenyataan bahwa g ++ 4.7.2 segfaults pada kode ini ( http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57770 ), kompiler lain yang diuji memberikan pesan kesalahan yang tidak menjelaskan banyak.

g ++ 4.8.1:

test.cpp: In constructor ‘constexpr A::B::B()’:
test.cpp:3:12: error: constructor required before non-static data member for ‘A::B::i’ has been parsed
     struct B
            ^
test.cpp: At global scope:
test.cpp:11:23: note: synthesized method ‘constexpr A::B::B()’ first required here 
     A(const B& _b = B())
                       ^

clang ++ 3.2 dan 3.3:

test.cpp:11:21: error: defaulted default constructor of 'B' cannot be used by non-static data member initializer which appears before end of class definition
    A(const B& _b = B())
                    ^

Membuat kode ini dapat dikompilasi adalah mungkin dan sepertinya tidak ada bedanya. Ada dua pilihan:

struct B
{
    int i = 0;
    B(){} // using B()=default; works only for clang++
};

atau

struct B
{
    int i;
    B() : i(0) {} // classic c++98 initialization
};

Apakah kode ini benar-benar salah atau apakah penyusunnya salah?

etam1024
sumber
3
G ++ 4.7.3 saya mengatakan internal compiler error: Segmentation faultke kode ini ...
Fred Foo
2
(kesalahan C2864: 'A :: B :: i': hanya anggota data integral const statis yang dapat diinisialisasi dalam kelas) adalah apa yang dikatakan VC2010. Keluaran itu sesuai dengan g ++. Clang juga mengatakannya, meskipun itu jauh lebih tidak masuk akal. Anda tidak dapat membuat default variabel dalam struct dengan melakukan int i = 0kecuali jika memang demikian static const int i = 0.
Chris Cooper
@Borgleader: BTW Saya akan menghindari godaan untuk memikirkan ekspresi B()sebagai panggilan fungsi ke konstruktor. Anda tidak pernah secara langsung "memanggil" konstruktor. Pikirkan ini sebagai sintaks khusus yang membuat sementara B... dan konstruktor dipanggil hanya sebagai satu bagian dari proses itu, jauh di dalam mekanisme yang mengikutinya.
Balapan Ringan di Orbit
2
Hmm, menambahkan konstruktor Btampaknya membuat ini berfungsi gcc 4.7.
Shafik Yaghmour
7
Menariknya, memindahkan definisi konstruktor A dari A juga tampaknya membuatnya berfungsi (g ++ 4.7); yang berbunyi dengan "konstruktor default default tidak dapat digunakan ... sebelum akhir definisi kelas".
moonshadow

Jawaban:

84

Apakah kode ini benar-benar salah atau apakah penyusunnya salah?

Baik. Standar memiliki cacat - ia mengatakan bahwa keduanya Adianggap lengkap saat mengurai penginisialisasi untuk B::i, dan yang B::B()(yang menggunakan penginisialisasi untuk B::i) dapat digunakan dalam definisi A. Itu jelas siklik. Pertimbangkan ini:

struct A {
  struct B {
    int i = (A(), 0);
  };
  A() noexcept(!noexcept(B()));
};

Ini memiliki kontradiksi: B::B()secara implisit noexceptIFF A()tidak melempar, dan A()tidak membuang IFF B::B()adalah tidak noexcept . Ada sejumlah siklus dan kontradiksi lain di area ini.

Ini dilacak oleh masalah inti 1360 dan 1397 . Secara khusus perhatikan catatan ini dalam masalah inti 1397:

Mungkin cara terbaik untuk menangani hal ini adalah dengan membuatnya tidak tepat bagi penginisialisasi anggota data non-statis untuk menggunakan konstruktor default kelasnya.

Itu adalah kasus khusus dari aturan yang saya terapkan di Clang untuk mengatasi masalah ini. Aturan Clang adalah bahwa konstruktor default default untuk kelas tidak dapat digunakan sebelum penginisialisasi anggota data non-statis untuk kelas tersebut diurai. Oleh karena itu, Clang mengeluarkan diagnosis di sini:

    A(const B& _b = B())
                    ^

... karena Clang mem-parsing argumen default sebelum mem-parsing penginisialisasi default, dan argumen default ini akan mengharuskan Bpenginisialisasi default sudah diurai (untuk mendefinisikan secara implisit B::B()).

Richard Smith
sumber
Senang mendengarnya. Tetapi pesan kesalahan masih menyesatkan, karena konstruktor sebenarnya tidak "digunakan oleh penginisialisasi anggota data non-statis".
aschepler
Tahukah Anda hal ini karena pengalaman masa lalu tertentu dengan masalah ini, atau hanya dengan membaca standar (dan daftar cacat) dengan cermat? Juga, +1.
Cornstalks
1 untuk jawaban mendetail ini. Jadi, apa jalan keluarnya? Semacam "penguraian kelas 2-fase", di mana penguraian anggota kelas luar yang bergantung pada kelas dalam ditunda sampai kelas dalam telah sepenuhnya terbentuk?
TemplateRex
4
@aschepler Ya, diagnostik di sini tidak terlalu baik. Saya telah mengajukan llvm.org/PR16550 untuk itu.
Richard Smith
@ Cornstalks Saya menemukan masalah ini saat menerapkan penginisialisasi untuk anggota data non-statis di Clang.
Richard Smith
0

Mungkin ini masalahnya:

§12.1 5. Sebuah konstruktor default yang default dan tidak didefinisikan sebagai dihapus secara implisit didefinisikan ketika digunakan (3.2) untuk membuat objek dari jenis kelasnya (1.8) atau ketika secara eksplisit default setelah deklarasi pertamanya

Jadi, konstruktor default dibuat saat pertama kali dicari, tetapi pencarian akan gagal karena A tidak sepenuhnya ditentukan dan B di dalam A tidak akan ditemukan.

fscan
sumber
Saya tidak yakin tentang itu "karena itu". Jelas B bbukan masalah, dan menemukan metode eksplisit / konstruktor yang dideklarasikan secara eksplisit Bbukanlah masalah. Jadi alangkah baiknya untuk melihat beberapa definisi mengapa pencarian harus diproses secara berbeda di sini sehingga " Bdi dalam Atidak ditemukan" hanya dalam satu kasus ini tetapi tidak dalam kasus lainnya, sebelum kita dapat menyatakan kode ilegal karena alasan ini.
moonshadow
Saya menemukan kata-kata dalam standar yang definisi kelasnya dianggap lengkap selama inisialisasi di dalam kelas, termasuk di dalam kelas bersarang. Saya tidak repot-repot mencatat referensi karena sepertinya tidak relevan.
Mark B
@moonshadow: pernyataan mengatakan konstruktor secara implisit default didefinisikan ketika odr- digunakan. secara eksplisit didefinisikan setelah deklarasi pertama. Dan B b tidak memanggil konstruktor, konstruktor A memanggil konstruktor B
fscan
Jika hal seperti ini adalah masalahnya, kode tersebut masih tidak valid jika Anda menghapus =0dari i = 0;. Tetapi tanpa itu =0, kodenya valid dan Anda tidak akan menemukan kompiler tunggal yang mengeluh tentang penggunaan B()dalam definisi A.
aschepler