Home > OS >  Why the tuple has a larger size than expected?
Why the tuple has a larger size than expected?

Time:08-26

I had the following definition of a tuple class template and tests for its size.

template <typename...> struct Tuple;

template <> struct Tuple<> {};

template <typename Head, typename... Tail>
struct Tuple<Head, Tail...> : Tuple<Tail...>
{
  Tuple(const Head& head, const Tail&... tail)
    : Base{ tail... }, m_head{ head } {}

private:
  using Base = Tuple<Tail...>;
  Head m_head;
};

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>

struct Nil {};

TEST_CASE("test size")
{
  using T0 = Tuple<>;
  CHECK(sizeof(T0) == 1);

  using T1 = Tuple<double, std::string, int, char>;

  struct Foo
  {
    double d;
    std::string s;
    int i;
    char c;
  };

  CHECK(sizeof(T1) == sizeof(Foo));

  using T2 = Tuple<int*, Nil>;
  CHECK(sizeof(T2) == sizeof(int*));

  using T3 = Tuple<int*, Nil, Nil>;
  CHECK(sizeof(T3) == sizeof(int*));
}

I expect because of the empty base class optimization the T2 and T3 tuples to be a pointer size, but the result is different.

[doctest] doctest version is "2.4.9"
[doctest] run with "--help" for options
===============================================================================
C:\projects\cpp\cpp_programming_language\28_metaprogramming\variadic_tuple.cpp(21):
TEST CASE:  test size

C:\projects\cpp\cpp_programming_language\28_metaprogramming\variadic_tuple.cpp(39): ERROR: CHECK( sizeof(T2) == sizeof(int*) ) is NOT correct!
  values: CHECK( 16 == 8 )

C:\projects\cpp\cpp_programming_language\28_metaprogramming\variadic_tuple.cpp(42): ERROR: CHECK( sizeof(T3) == sizeof(int*) ) is NOT correct!
  values: CHECK( 16 == 8 )

===============================================================================
[doctest] test cases: 1 | 0 passed | 1 failed | 0 skipped
[doctest] assertions: 4 | 2 passed | 2 failed |
[doctest] Status: FAILURE!

Why is this and is it possible somehow to enable the empty base class optimization?

CodePudding user response:

Empty base optimization only applies when you derived from an empty class. In your case, Tuple<> and Nil are empty classes, while Tuple<Nil> is not since it has non-static members (taking an address).

You have already enjoyed EBO in your implementation. Tuple<int*> is derived from Tuple<>, which is empty, so sizeof(Tuple<int*>) == sizeof(int*). You don't need extra space for the empty base class here.

Since C 20, you could make Tuple<Nil>, Tuple<Nil, Nil> empty using attributes [[no_unique_address]]

template <typename... Tails>
struct Tuple<Nil, Tails...> {
    using Base = Tuple<Tails...>;
    [[no_unique_address]] Nil m_head;
};

You tell the compiler, that even though I have a member, I don't want it to occupy any space. Now sizeof(Tuple<int*, Nil>) == sizeof(int*) works.

Demo

  • Related