Aku bermain-main dengan [[no_unique_address]]
di c++20
.
Dalam contoh pada cppreference kita memiliki tipe Empty
dan tipe kosongZ
struct Empty {}; // empty class
struct Z {
char c;
[[no_unique_address]] Empty e1, e2;
};
Rupanya, ukuran Z
harus setidaknya 2
karena jenis e1
dan e2
sama.
Namun, saya benar-benar ingin memiliki Z
ukuran 1
. Ini membuat saya berpikir, bagaimana dengan membungkus Empty
beberapa kelas pembungkus dengan parameter templat tambahan yang memberlakukan berbagai jenis e1
dan e2
.
template <typename T, int i>
struct Wrapper : public T{};
struct Z1 {
char c;
[[no_unique_address]] Wrapper<Empty,1> e1;
[[no_unique_address]] Wrapper<Empty,2> e2;
};
Sayangnya sizeof(Z1)==2
,. Apakah ada trik untuk membuat ukuran Z1
menjadi satu?
Saya menguji ini dengan gcc version 9.2.1
danclang version 9.0.0
Dalam aplikasi saya, saya punya banyak jenis formulir kosong
template <typename T, typename S>
struct Empty{
[[no_unique_address]] T t;
[[no_unique_address]] S s;
};
Yang merupakan tipe kosong jika T
dan S
juga tipe kosong dan berbeda! Saya ingin jenis ini kosong bahkan jika T
dan S
merupakan tipe yang sama.
T
dirinya sendiri? Itu akan menghasilkan tipe yang berbeda. Sekarang fakta bahwa keduaWrapper
warisanT
ituT
? Sekarang,T
adalah argumen templat.T
.Jawaban:
Anda tidak bisa mendapatkannya. Secara teknis, Anda bahkan tidak dapat menjamin bahwa itu akan kosong walaupun
T
danS
merupakan tipe kosong yang berbeda. Ingat:no_unique_address
adalah atribut; kemampuannya untuk menyembunyikan objek sepenuhnya tergantung pada implementasi. Dari perspektif standar, Anda tidak bisa memaksakan ukuran objek kosong.Saat implementasi C ++ 20 matang, Anda harus menganggap bahwa
[[no_unique_address]]
umumnya akan mengikuti aturan optimasi basis kosong. Yaitu, selama dua objek dari jenis yang sama bukan sub-objek, Anda mungkin bisa berharap untuk bersembunyi. Tetapi pada titik ini, itu semacam keberuntungan.Mengenai kasus spesifik
T
danS
jenis yang sama, itu sama sekali tidak mungkin. Terlepas dari implikasi nama "no_unique_address", kenyataannya adalah bahwa C ++ mengharuskan, mengingat dua pointer ke objek dari tipe yang sama, pointer tersebut menunjuk ke objek yang sama atau memiliki alamat yang berbeda. Saya menyebutnya "aturan identitas unik", danno_unique_address
tidak memengaruhi hal itu. Dari [intro.object] / 9 :Anggota tipe kosong dinyatakan sebagai
[[no_unique_address]]
berukuran nol, tetapi memiliki tipe yang sama membuat ini tidak mungkin.Memang, memikirkannya, berusaha menyembunyikan tipe kosong melalui sarang masih melanggar aturan identitas yang unik. Pertimbangkan Anda
Wrapper
danZ1
kasus. Diberikanz1
yang merupakan contoh dariZ1
, jelas bahwaz1.e1
danz1.e2
adalah objek yang berbeda dengan tipe yang berbeda. Namun,z1.e1
tidak bersarang di dalamz1.e2
atau sebaliknya. Dan sementara mereka memiliki jenis yang berbeda,(Empty&)z1.e1
dan(Empty&)z1.e2
yang tidak jenis yang berbeda. Tetapi mereka menunjuk ke objek yang berbeda.Dan dengan aturan identitas unik, mereka harus memiliki alamat yang berbeda. Jadi meskipun
e1
dane2
secara nominal berbeda jenis, bagian dalamnya juga harus mematuhi identitas unik terhadap sub-objek lain dalam objek yang berisi yang sama. Secara rekursif.Apa yang Anda inginkan tidak mungkin dilakukan dalam C ++ seperti saat ini, terlepas dari bagaimana Anda mencoba.
sumber
Sejauh yang saya tahu, itu tidak mungkin jika Anda ingin memiliki kedua anggota. Tetapi Anda dapat mengkhususkan dan hanya memiliki satu anggota ketika jenisnya sama dan kosong:
Tentu saja, sisa program yang menggunakan anggota perlu diubah untuk menangani kasus di mana hanya ada satu anggota. Seharusnya tidak masalah anggota mana yang digunakan dalam kasus ini - lagipula, itu adalah objek stateless tanpa alamat unik. Fungsi anggota yang ditampilkan harus sesederhana itu.
Anda dapat memperkenalkan lebih banyak spesialisasi untuk mendukung kompresi rekursif pasangan kosong:
Terlebih lagi, untuk mengompresi sesuatu seperti
Empty<Empty<A, char>, A>
.sumber
sizeof(Empty<Empty<A,A>,A>{})==2
manaA
adalah struct benar-benar kosong.get_empty<T>
fungsi. Kemudian Anda dapat menggunakan kembaliget_empty<T>
di kiri atau kanan jika sudah berfungsi di sana.