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?
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.