Referensi yang tidak terdefinisi ke char static constexpr []

186

Saya ingin memiliki static const chararray di kelas saya. GCC mengeluh dan mengatakan kepada saya bahwa saya harus menggunakan constexpr, meskipun sekarang memberitahu saya itu referensi yang tidak ditentukan. Jika saya membuat array menjadi non-anggota maka kompilasi. Apa yang sedang terjadi?

// .hpp
struct foo {
  void bar();
  static constexpr char baz[] = "quz";
};

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}
Pubby
sumber
1
Hanya firasat, apakah itu berfungsi jika baz int misalnya? Bisakah Anda mengaksesnya? Itu juga bisa menjadi bug.
FailedDev
1
@Ubby: Pertanyaan: Di unit terjemahan mana akan didefinisikan? Jawab: Semua yang menyertakan tajuk. Masalah: Melanggar aturan satu definisi. Pengecualian: Integral konstanta waktu kompilasi dapat "diinisialisasi" di header.
Mooing Duck
Ia mengkompilasi dengan baik sebagai int@MooingDuck. Ia berfungsi dengan baik sebagai non-anggota. Bukankah itu melanggar aturan juga?
Pubby
@ Pubby8: ints curang. Sebagai non-anggota, itu tidak boleh diizinkan, kecuali aturan berubah untuk C ++ 11 (mungkin)
Mooing Duck
Mempertimbangkan pandangan dan peningkatan, pertanyaan ini membutuhkan jawaban yang lebih rinci, yang saya tambahkan di bawah.
Shafik Yaghmour

Jawaban:

188

Tambahkan ke file cpp Anda:

constexpr char foo::baz[];

Alasan: Anda harus memberikan definisi anggota statis serta deklarasi. Deklarasi dan penginisialisasi masuk ke dalam definisi kelas, tetapi definisi anggota harus terpisah.

Kerrek SB
sumber
70
Itu terlihat aneh ... karena tampaknya tidak memberikan kompiler dengan beberapa informasi yang sebelumnya tidak ...
anggur
32
Terlihat lebih aneh ketika Anda memiliki deklarasi kelas di file .cpp! Anda menginisialisasi bidang dalam deklarasi kelas, tetapi Anda masih perlu " mendeklarasikan " bidang tersebut dengan menulis constexpr char foo :: baz [] di bawah kelas. Tampaknya programmer yang menggunakan constexpr dapat mengkompilasi program mereka dengan mengikuti satu tip aneh: nyatakan lagi.
Lukasz Czerwinski
5
@LukaszCzerwinski: Kata yang Anda cari adalah "define".
Kerrek SB
5
Benar, tidak ada informasi baru: nyatakan menggunakandecltype(foo::baz) constexpr foo::baz;
bukan pengguna
6
apa yang akan terlihat jika ekspresi foo templatized? Terima kasih.
Hei
80

C ++ 17 memperkenalkan variabel inline

C ++ 17 memperbaiki masalah ini untuk constexpr static variabel anggota yang membutuhkan definisi out-of-line jika itu digunakan odr. Lihat bagian kedua dari jawaban ini untuk detail pra-C ++ 17.

Proposal P0386 Inline Variables memperkenalkan kemampuan untuk menerapkan inlinespecifier ke variabel. Khususnya untuk kasus ini constexprmenyiratkan inlineuntuk variabel anggota statis. Proposal itu mengatakan:

Specifier sebaris dapat diterapkan ke variabel serta fungsi. Variabel yang dideklarasikan sebaris memiliki semantik yang sama dengan fungsi yang dinyatakan sebaris: ia dapat didefinisikan, secara identik, dalam beberapa unit terjemahan, harus didefinisikan dalam setiap unit terjemahan yang digunakan odr, dan perilaku program adalah seolah-olah hanya ada satu variabel.

dan memodifikasi [basic.def] p2:

Deklarasi adalah definisi kecuali
...

  • itu menyatakan anggota data statis di luar definisi kelas dan variabel didefinisikan dalam kelas dengan specifier constexpr (penggunaan ini sudah ditinggalkan; lihat [depr.static_constexpr]),

...

dan tambahkan [depr.static_constexpr] :

Untuk kompatibilitas dengan Standar Internasional C ++ sebelumnya, anggota data statis constexpr dapat redundansi redundansi di luar kelas tanpa inisialisasi. Penggunaan ini sudah usang. [Contoh:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)

 - contoh akhir]


C ++ 14 dan sebelumnya

Dalam C ++ 03, kami hanya diizinkan untuk menyediakan inisialisasi kelas untuk jenis integral integral atau enumerasi , dalam C ++ 11 menggunakan constexprini diperluas ke tipe literal .

Dalam C ++ 11, kita tidak perlu memberikan definisi lingkup namespace untuk anggota statis constexprjika tidak digunakan , kita dapat melihat ini dari draft bagian standar C ++ 11 9.4.2 [class.static.data] yang mengatakan ( tekankan tambang ke depan ):

[...] Anggota data statis dari tipe literal dapat dideklarasikan dalam definisi kelas dengan specifier constexpr; jika demikian, deklarasi harus menentukan inisialisasi brace-atau-sama-di mana setiap klausa initializer yang merupakan tugas-ekspresi adalah ekspresi konstan. [Catatan: Dalam kedua kasus ini, anggota dapat muncul dalam ekspresi konstan. —Kirim catatan] Anggota harus tetap didefinisikan dalam lingkup namespace jika digunakan odr (3.2) dalam program dan definisi lingkup namespace tidak boleh mengandung inisialisasi.

Jadi kemudian pertanyaannya adalah, apakah yang baz digunakan di sini:

std::string str(baz); 

dan jawabannya adalah ya , jadi kami memerlukan definisi lingkup namespace juga.

Jadi bagaimana kita menentukan apakah suatu variabel digunakan odr ? C ++ 11 yang asli kata-kata di bagian 3.2 [basic.def.odr] mengatakan:

Ekspresi berpotensi dievaluasi kecuali operan yang tidak dievaluasi (Klausul 5) atau subekspresi daripadanya. Variabel yang namanya muncul sebagai ekspresi yang berpotensi dievaluasi adalah odr-digunakan kecuali itu adalah objek yang memenuhi persyaratan untuk muncul dalam ekspresi konstan (5.19) dan konversi nilai-ke-nilai (4.1) segera diterapkan .

Begitu bazjuga menghasilkan ekspresi konstan tetapi konversi lvalue ke rvalue tidak segera diterapkan karena tidak berlaku karena bazmenjadi array. Ini tercakup dalam bagian4.1 [conv.lval] yang mengatakan:

Nilai (3,10) dari tidak berfungsinya, T-non-array tipe dapat dikonversi ke nilai awal.53 [...]

Apa yang diterapkan dalam konversi array-to-pointer .

Kata-kata [basic.def.odr] ini diubah karena Laporan Cacat 712 karena beberapa kasus tidak dicakup oleh kata-kata ini tetapi perubahan ini tidak mengubah hasil untuk kasus ini.

Shafik Yaghmour
sumber
jadi apakah kita jelas constexprtidak ada hubungannya sama sekali dengan itu? ( bazadalah ekspresi yang konstan)
MM
@MattMcNabb well constexpr diperlukan jika anggota bukan integral or enumeration typetetapi sebaliknya, ya, yang penting adalah bahwa itu adalah ekspresi yang konstan .
Shafik Yaghmour
Dalam paragraf pertama "ord-used" harus dibaca sebagai "odr-used", saya percaya, tapi saya tidak pernah yakin dengan C ++
Egor Pasko
37

Ini benar-benar cacat dalam C ++ 11 - seperti yang orang lain jelaskan, dalam C ++ 11 variabel anggota constexpr statis, tidak seperti setiap jenis variabel global constexpr lainnya, memiliki hubungan eksternal, sehingga harus secara eksplisit didefinisikan di suatu tempat.

Perlu juga dicatat bahwa dalam praktiknya Anda sering dapat lolos dengan variabel anggota constexpr statis tanpa definisi saat kompilasi dengan optimasi, karena mereka dapat berakhir dengan sebaris di semua penggunaan, tetapi jika Anda mengkompilasi tanpa optimasi sering kali program Anda akan gagal untuk ditautkan. Ini membuat ini jebakan tersembunyi yang sangat umum - program Anda mengkompilasi dengan baik dengan optimasi, tetapi segera setelah Anda mematikan optimasi (mungkin untuk debugging), ia gagal untuk terhubung.

Kabar baiknya - cacat ini diperbaiki di C ++ 17! Pendekatannya agak berbelit-belit: dalam C ++ 17, variabel anggota constexpr statis secara implisit sejalan . Memiliki inline diterapkan pada variabel adalah konsep baru dalam C ++ 17, tetapi secara efektif berarti bahwa mereka tidak memerlukan definisi eksplisit di mana saja.

SethML
sumber
4
Naik untuk info C ++ 17. Anda dapat menambahkan info ini ke jawaban yang diterima!
SR
5

Bukankah solusi yang lebih elegan mengubah char[]menjadi:

static constexpr char * baz = "quz";

Dengan cara ini kita dapat memiliki definisi / deklarasi / inisialisasi dalam 1 baris kode.

deddebme
sumber
9
dengan char[]Anda dapat menggunakan sizeofuntuk mendapatkan panjang string pada waktu kompilasi, dengan char *Anda tidak bisa (itu akan mengembalikan lebar tipe pointer, 1 dalam kasus ini).
gnzlbg
2
Ini juga menghasilkan peringatan jika Anda ingin ketat dengan ISO C ++ 11.
Shital Shah
Lihat jawaban saya yang tidak menunjukkan sizeofmasalah ini, dan dapat digunakan dalam solusi "hanya header"
Josh Greifer
4

Solusi saya untuk tautan eksternal anggota statis adalah dengan menggunakan constexprgetter anggota referensi (yang tidak mengalami masalah @gnzlbg yang dimunculkan sebagai komentar atas jawaban dari @deddebme).
Ungkapan ini penting bagi saya karena saya tidak suka memiliki banyak file .cpp di proyek saya, dan mencoba membatasi jumlahnya menjadi satu, yang hanya terdiri dari #includes dan sebuah main()fungsi.

// foo.hpp
struct foo {
  static constexpr auto& baz() { return "quz"; }
};

// some.cpp

  auto sz = sizeof(foo::baz()); // sz == 4

  auto& foo_baz = foo::baz();  // note auto& not auto
  auto sz2 =  sizeof(foo_baz);    // 4
  auto name = typeid(foo_baz).name();  // something like 'char const[4]'
Josh Greifer
sumber
-1

Di lingkungan saya, gcc vesion adalah 5.4.0. Menambahkan "-O2" dapat memperbaiki kesalahan kompilasi ini. Tampaknya gcc dapat menangani kasus ini saat meminta optimasi.

Haishan Zhou
sumber