Mengapa kelas saya tidak dapat dibangun-standar?

28

Saya memiliki kelas-kelas itu:

#include <type_traits>

template <typename T>
class A {
public:
    static_assert(std::is_default_constructible_v<T>);

};

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

    A<C> a_m;
};

int main() {
    A<B::C> a;
}

Saat kompilasi, a_mtidak bisa dibangun tetapi default a.

Saat mengubah Cke:

struct C {
      int i;
   };

semuanya baik-baik saja.

Diuji dengan Dentang 9.0.0.

Nicolas
sumber
3
GCC 8.3 - OK, GCC 9.1 / 9.2 - Gagal.
Evg
2
Dengan C() {}itu bekerja juga.
Evg
3
Ini bau kereta bagiku. Tidak ada kecocokan langsung yang jelas di Bugzilla.
Lightness Races in Orbit
2
Menarik: static_assertin Agagal, tetapi jika Anda sebaliknya membangun bagian Tdalam A(misalnya menempatkan anggota di T t;sana), itu berfungsi dengan baik. Ketidakkonsistenan antara apa yang dikatakan oleh sifat itu dan apa yang sebenarnya mungkin terjadi ...
sebrockm
2
@Nicolas Benar, tapi itu karena beberapa kasus tepi, tidak ada yang berlaku di sini (khususnya, seperti kalimat yang sama pada cppreference mengatakan, const int x;tidak valid tanpa penginisialisasi, murni karena constdan perilaku inisialisasi tipe bawaan dan beberapa sejarah)
Lightness Races di Orbit

Jawaban:

9

Ini dilarang baik oleh teks standar dan oleh beberapa implementasi utama seperti yang disebutkan dalam komentar, tetapi untuk alasan yang sama sekali tidak terkait.

Pertama, alasan "menurut buku": titik instantiasi A<C>adalah, menurut standar, tepat sebelum definisiB , dan titik instantiasi std::is_default_constructible<C>adalah tepat sebelum itu:

Untuk spesialisasi templat kelas, [...] jika spesialisasi secara implisit instantiated karena dirujuk dari dalam spesialisasi templat lain, jika konteks dari mana spesialisasi dirujuk tergantung pada parameter templat, dan jika spesialisasi tidak instantiated sebelumnya ke instantiation dari template yang dilampirkan, titik instantiation adalah tepat sebelum titik instantiation dari template yang melampirkan. Kalau tidak, titik instantiasi untuk spesialisasi seperti itu segera mendahului deklarasi ruang lingkup atau definisi namespace yang mengacu pada spesialisasi.

Karena Cjelas tidak lengkap pada saat itu, perilaku instantiating std::is_default_constructible<C>tidak terdefinisi. Namun, lihat masalah inti 287 , yang akan mengubah aturan ini.


Pada kenyataannya, ini ada hubungannya dengan NSDMI.

  • NSDMI aneh karena tertunda parsing - atau dalam bahasa standar mereka adalah "konteks kelas lengkap".
  • Dengan demikian, yang = 0pada prinsipnya dapat merujuk pada hal-hal yang Bbelum dinyatakan, sehingga implementasi tidak dapat benar-benar mencoba menguraikannya sampai selesai B.
  • Menyelesaikan kelas mengharuskan deklarasi implisit fungsi anggota khusus, khususnya konstruktor default, karena Ctidak ada konstruktor yang dideklarasikan.
  • Bagian dari deklarasi itu (keteguhan, ketidak-kecurangan) tergantung pada sifat-sifat NSDMI.
  • Jadi, jika kompiler tidak dapat mengurai NSDMI, ia tidak dapat menyelesaikan kelas.
  • Akibatnya, pada saat instantiate A<C>, ia berpikir bahwa Citu tidak lengkap.

Seluruh area yang berurusan dengan wilayah yang diurai-tertunda ini sangat tidak ditentukan, disertai dengan divergensi implementasi. Mungkin perlu beberapa saat sebelum dibersihkan.

TC
sumber
0

Perilaku yang tidak terdefinisi adalah:

Jika instantiasi templat di atas tergantung, secara langsung atau tidak langsung, pada tipe yang tidak lengkap, dan instantiasi itu dapat menghasilkan hasil yang berbeda jika tipe tersebut diselesaikan secara hipotetis, perilaku tidak terdefinisi.

Pelupaan
sumber
7
Mengapa C tidak lengkap?
interjay
1
@interjay, Csudah selesai, tetapi Btidak. Dan B::Ctergantung secara tidak langsung pada B.
Evg
1
@ Evg Teks "Tergantung langsung atau tidak langsung" hanya muncul di cppreference.com. Standar hanya mengatakan bahwa tipe T harus lengkap.
interjay
2
@interjay saya menulis sebagian besar kata-kata ini. Yang ingin kami katakan adalah bahwa 1) jika Anda membuat sifat dengan cara yang dapat memicu pelanggaran ODR nanti ketika beberapa tipe tidak lengkap selesai, itu tidak terdefinisi; dan 2) itu tidak terdefinisi bahkan jika Anda tidak benar - benar menyebabkan pelanggaran ODR dalam program Anda, sehingga implementasi perpustakaan standar dapat memilih untuk mendiagnosis bahwa pada titik sifat tersebut digunakan jika mereka menginginkannya. Jika Cmemiliki templat konstruktor default dengan beberapa SFINAE aneh yang dapat mengubah jawaban jika diisi Bsecara berbeda, maka tentu saja, sifatnya bergantung pada itu.
TC