2

I'm interested in having tuples with "holes" in them. These holes are an empty struct. All holes will have the same type, called Empty here.

For illustration purposes, let

struct DByte {
    std::array<std::byte, 2> data;
};

struct Empty {
};

. As I expected,

sizeof(std::tuple<DByte, Empty, Empty>) = 2

. However,

sizeof(std::tuple<Empty, DByte, Empty>) = 3
sizeof(std::tuple<Empty, Empty, DByte>) = 3

and I can't understand why these last two types aren't of size 2 (on a platform with sizeof(std::byte) == 1 && alignof(std::byte) == 1).

This demonstration also prints the memory layout of these types under Clang and GCC.

Why aren't all these tuples the same size with the following layout:

0       1       2
|     DByte     |
| Empty | Empty |

?

Bernardo Sulzbach
  • 1,293
  • 10
  • 26
  • 1
    they dont have that layout. Just recently heard a talk that mentioned one implementation that keeps the tuples elements in reverse order, it can be different – 463035818_is_not_an_ai Mar 26 '21 at 12:07
  • related/dupe: https://stackoverflow.com/questions/27663641/why-does-libstdc-store-stdtuple-elements-in-reverse-order – NathanOliver Mar 26 '21 at 12:10
  • 2
    Notice that MSVC is "coherent" with always 4 [Demo](https://godbolt.org/z/fE1hnKPx6). – Jarod42 Mar 26 '21 at 13:01
  • Notice that you have similar result with regular struct member [Demo](https://godbolt.org/z/3ofW4f4z6) (With `[[no_unique_address]]`). – Jarod42 Mar 26 '21 at 13:19
  • "*As I expected,*" I wouldn't expect that. In fact, I'm pretty sure that `2` is the *wrong answer* for it. That the compiler is violating the specification. – Nicol Bolas Mar 26 '21 at 13:55
  • @NicolBolas can you elaborate on what you mean by "the compiler is violating the specification"? – Bernardo Sulzbach Mar 26 '21 at 16:27

2 Answers2

3

Why aren't all these tuples the same size with the following layout:

Because the implementations of std::tuple do not re-order their sub objects based on the types of the contained objects (although they are not required to not do so). They use one order that depends on the order of parameters, but not on the types of those parameters.

Normally, all sub objects are required by the standard to have a unique address, and there may not be overlap between sub objects of a standard layout class. There is an exception1 for empty base classes, which are not required to have a unique address, and that special rule is what some std::tuple implementations take advantage of to make empty sub objects use no memory. But it only works when the order of the sub objects is correct for the exception to apply.

1 C++20 adds another exception, but the standard libraries implemented std::tuple in C++11 and cannot practically change their layout without breaking ABI, so that is not important to the implementation of std::tuple. The C++20 feature wouldn't improve the optimisation without re-ordering the sub objects either.

eerorika
  • 232,697
  • 12
  • 197
  • 326
0

Why aren't all these tuples the same size with the following layout:

They might have, it is just a optimization miss.

You might see the same miss for regular classes:

struct A
{
    [[no_unique_address]]DByte m;
    [[no_unique_address]]Empty e1;
    [[no_unique_address]]Empty e2;
};

struct B
{
    [[no_unique_address]]Empty e1;
    [[no_unique_address]]DByte m;
    [[no_unique_address]]Empty e2;
};

struct C
{
    [[no_unique_address]]Empty e1;
    [[no_unique_address]]Empty e2;
    [[no_unique_address]]DByte m;
};

Demo

Notice that msvc gives same size for your different tuples Demo. (always 4, so without empty class optimization).

Layout of tuple is not specified. So all those are possible.

Jarod42
  • 203,559
  • 14
  • 181
  • 302