16

The implementations of std::tuple in both libstdc++ and libc++ use (I presume) the empty base class optimisation to compress empty elements:

struct empty {};

static_assert(sizeof(std::tuple<int, empty>) == sizeof(int), ""); // (1)

My question is simply, is this behaviour mandated by the standard? That is, can I rely on (1) always being true for a standard-conforming implementation?

Tristan Brindle
  • 16,281
  • 4
  • 39
  • 82
  • 2
    Empty base class optimisation itself is not mandated by standard. – Revolver_Ocelot Mar 11 '16 at 13:00
  • This is a different optimisation from "empty base class", as `empty` is not a base class of the tuple. – molbdnilo Mar 11 '16 at 13:16
  • @molbdnilo In his answer to [this question](http://stackoverflow.com/questions/9641699/why-is-it-not-good-to-use-recursive-inheritance-for-stdtuple-implementations?rq=1), Howard Hinnant (who wrote the libc++ tuple implementation) says it "optimizes away space for empty components using the compiler's empty base class optimisation facility" – Tristan Brindle Mar 11 '16 at 13:20
  • @TristanBrindle Interesting trick, from the "any problem can be solved by introducing a level of indirection" book. I had literally no idea. Thanks. – molbdnilo Mar 11 '16 at 13:30

2 Answers2

12

No, that is not guaranteed. C++ 11 § 20.4 (the chapter about std::tuple) does not mention the size of the type at all. So there are no guarantees about how the members in a tuple are organised. Any empty-base optimisation and similar effects are purely a quality-of-implementation issue.

Note that this means there is even no guarantee that std::tuple<int, char> will be stored in memory as int followed by char and not vice versa. The layout of a std::tuple object is completely unspecified.

Niall
  • 30,036
  • 10
  • 99
  • 142
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
8

No. It is not. Actually there are places when you cannot have empty base class optimisation at all.

C++ standard mandates that two distinct objects should have different addresses.

consider following: std::tuple<char, empty, empty>. No matter how you look (composition, inheritance), you have two empty objects, which should have different addresses. This will increase size of tuple by at least 1.

Problem can arise from indirect inheritance:

struct derived: empty
{
    char i;
};
std::tuple<derived, empty>

Here we have two empty objects inside same class, and they have to have different addresses too, so you cannot optimise away second empty member of tuple.

EDIT: found unexpected behaviour with empty bases and aligned_storage stemming from agressive EBO: http://coliru.stacked-crooked.com/a/f45de2f889151ea3

Revolver_Ocelot
  • 8,609
  • 3
  • 30
  • 48
  • 1
    On both gcc and clang, `sizeof(std::tuple)` is 4. – Barry Mar 11 '16 at 13:34
  • @Barry fixed it. Literally just thought about a way to avoid increasing size in some cases and decided to check the standard. Now new, unavoidable example. – Revolver_Ocelot Mar 11 '16 at 13:39
  • @Revolver_Ocelot If I was smart enough `tuple`, I could realize that empty and derived are a POD types with no alignment requirements. So I could put it manually inside myself, overlapping with derived? – Yakk - Adam Nevraumont Mar 11 '16 at 14:19
  • @Yakk I am not sure if it is allowed, actually. Anyway, I fixed example to make EBO is impossible even if it is allowed. – Revolver_Ocelot Mar 11 '16 at 14:32
  • 2
    "two distinct objects should have different addresses" -- two distinct objects *of the same type*. That's why the empty base class optimization requires that the first data member of the derived class, doesn't have the same type as the base being elided. It's going to end up with the same address as the elided base. – Steve Jessop Mar 11 '16 at 17:39
  • @Barry [with Clang with libc++ it's 8](http://melpon.org/wandbox/permlink/f1CxJ09tNc0V4z0q) – TemplateRex Apr 20 '16 at 08:20