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.