|
|
|
|
|
by celrod
703 days ago
|
|
C++20 added `[[no_unique_address]]`, which lets a `std::is_empty` field alias another field, so long as there is only 1 field of that `is_empty` type.
https://godbolt.org/z/soczz4c76
That is, example 0 shows 8 bytes, for an `int` plus an empty field.
Example 1 shows two empty fields with the `int`, but only 4 bytes thanks to `[[no_unique_address]]`.
Example 2 unfortunately is back up to 8 bytes because we have two empty fields of the same type... `[[no_unique_address]]` is far from perfect, and inherited the same limitations that inheriting from an empty base class had (which was the trick you had to use prior to C++20).
The "no more than 1 of the same type" limitation actually forced me to keep using CRTP instead of making use of "deducing this" after adopting c++23: a `static_assert` on object size failed, because an object grew larger once an inherited instance, plus an instance inherited by a field, no longer had different template types. So, I agree that it is annoying and seems totally unnecessary, and has wasted my time; a heavy cost for a "feature" (empty objects having addresses) I have never wanted.
But, I still make a lot of use of empty objects in C++ without increasing the size of any of my non-empty objects. C++20 concepts are nice for writing generic code, but (from what I have seen, not experienced) Rust traits look nice, too. |
|
You may need to sit down. An empty type has no values. Not one value, like the unit type which C++ makes a poor job of as you explain, but no values. None at all.
Because it has no values we will never be called upon to store one, we can't call functions which take one as a parameter, operations whose result is an empty type must diverge (ie control flow escapes, we never get to use the value because there isn't one). Code paths which are predicated on the value of an empty type are dead and can be pruned. And so on.
Rust uses this all over the place. C++ can't express it.