10

This is mostly trivia question, as I doubt that I will ever need this space saving.

While playing around on godbolt I noticed that both libstdc++ and libc++ implementations of std::variant require more than 1 byte to store variant of empty structs.

libstc++ uses 2 bytes

libc++ uses 8 bytes

I presume it is just not worth the trouble to optimize this, but I wonder if there is any other reason. In particular is there something in standard wording for std::variant that prevents this optimization.

NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277
  • it wont make a difference but you have `-O3` followed by `-O2` – 463035818_is_not_an_ai Sep 03 '21 at 19:28
  • 5
    Variant stores an index, that's at least 1 byte. Variant stores a value, that's also at least 1 byte because you're allowed to take an address of it. How could it be less than 2 bytes? – Evg Sep 03 '21 at 19:31
  • @Evg one byte for index, 0 bits for storage. – NoSenseEtAl Sep 03 '21 at 19:35
  • 2
    @Evg Empty objects can share a storage location with the index. – Yakk - Adam Nevraumont Sep 03 '21 at 19:36
  • 1
    Please provide a [mcve] inline in your question, not via an external link! Also, what binary layout of that type would you expect? – Ulrich Eckhardt Sep 03 '21 at 19:40
  • imho of someone who doesn't write standard libraries, size 2 is a reasonable expectation for optimization. size 1 could be exceedingly difficult, especially since [`no_unique_address`](https://en.cppreference.com/w/cpp/language/attributes/no_unique_address) current implementation is hit and miss. – bolov Sep 03 '21 at 19:42
  • @bolov I think they can use conditional inheritance. Again I am also not writing std libs :) – NoSenseEtAl Sep 03 '21 at 20:01

1 Answers1

10

Every object takes up at least 1 byte of space. The counter itself needs to take up at least 1 byte, but you also need space for the potential choices of object. Even if you use a union, it still needs to be one byte. And it can't be the same byte as the counter.

Now, you might think that no_unique_address could just come to the rescue, permitting the member union to overlap with the counter if all of the union elements are empty. But consider this code:

empty_type e{};
variant<empty_type> ve{in_place_index<0>}; //variant now stores the empty type.
auto *pve = ve.get_if<0>(); //Pointer to an `empty_type`.
memcpy(pve, &e, sizeof(empty_type)); //Copying from one trivial object to another.

The standard does not say that the members of a variant are "potentially-overlapping subojects" of variant or any of its internal members. Therefore, it is 100% OK for a user to do a memcpy from one trivial empty object to the other.

Which will overwrite the counter if it were overlapping with it. Therefore, it cannot be overlapping with it.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 2
    I did not know that `std::variant` is required to be `trivially_copyable` if all of its types are. Very good point. – bolov Sep 03 '21 at 20:32
  • So what you are saying is that I should write a proposal for C++23 that disables get(_if) when all members of variant are empty. ;) – NoSenseEtAl Sep 03 '21 at 20:37
  • @NoSenseEtAl: It would be better to write a proposal such that members of a `tuple` and/or `variant` may be potentially overlapping subobjects of their respective containing object. – Nicol Bolas Sep 03 '21 at 20:52
  • @NicolBolas but wouldn't that break code you wrote?, like runtime UB? – NoSenseEtAl Sep 03 '21 at 21:47
  • @bolov: While that is true, that wasn't the point. I'm doing a trivial copy to the object inside the variant, not the variant itself. – Nicol Bolas Sep 04 '21 at 02:20