Home > Enterprise >  Why this two different implementations of Tuple have a different size?
Why this two different implementations of Tuple have a different size?

Time:08-27

I have two different implementations of a Tuple class template. One with specialization for any number of arguments and one using variadic templates. When using an empty class for some of the tuple elements the two implementations have different sizes. Why does the second one using a variadic template have a bigger size and is it possible to be fixed to have the same size as the first one?

#include <iostream>

using namespace std;

struct Nil {};

template <typename T1 = Nil, typename T2 = Nil>
struct Tuple1 : Tuple1<T2>
{
  T1 x;

  using Base = Tuple1<T2>;

  Base* base() { return static_cast<Base*>(this); }
  const Base* base() const { return static_cast<const Base*>(this); }

  Tuple1(const T1& t1, const T2& t2)
    : Base{ t2 }, x{ t1 } {}
};

template <> struct Tuple1<> {};

template <typename T1>
struct Tuple1<T1> : Tuple1<>
{
  T1 x;

  using Base = Tuple1<>;

  Base* base() { return static_cast<Base*>(this); }
  const Base* base() const { return static_cast<const Base*>(this); }

  Tuple1(const T1& t1)
    : Base{}, x{ t1 } {}
};

// ---------------------------------------------------------------------------

template <typename...> struct Tuple2;

template <> struct Tuple2<> {};

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

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

int main()
{
  cout << "Tuple1 sizes:\n";
  cout << sizeof(Tuple1<>) << '\n';
  cout << sizeof(Tuple1<int*>) << '\n';
  cout << sizeof(Tuple1<int*, Nil>) << '\n';

  cout << '\n';

  cout << "Tuple2 sizes:\n";
  cout << sizeof(Tuple2<>) << '\n';
  cout << sizeof(Tuple2<int*>) << '\n';
  cout << sizeof(Tuple2<int*, Nil>) << '\n';

  return 0;
}

The result of the execution of the program with MSVC 2022 is the following:

Tuple1 sizes:
1
8
8

Tuple2 sizes:
1
8
16

CodePudding user response:

Tuple1<int*> is Tuple1<int*, Nil> and have a specialization wich unique member T1 x; with empty base class Tuple1<Nil, Nil>.

On the other side, Tuple2 treat Nil as any other (empty) types.

With cppinsights, you might see instantiation:

template<>
struct Tuple2<Nil> : public Tuple2<>
{
  inline Tuple2(const Nil& head);

private: 
  using Base = Tuple2<>;
  Nil m_head;
};


template<>
struct Tuple2<int *, Nil> : public Tuple2<Nil>
{
  inline Tuple2(int *const& head, const Nil& __tail1);

private: 
  using Base = Tuple2<Nil>;
  int* m_head;
};

so Tuple2<int *, Nil> has 2 members:

  • int* m_head;
  • Nil Tuple2<Nil>::m_head.

CodePudding user response:

You can use MSVC's undocumented /d1reportAllClassLayout option to see how it lays out the classes.

class Tuple2<int *,struct Nil>  size(16):
     ---  
 0  |  --- (base class Tuple2<struct Nil>)  
 0  | |  --- (base class Tuple2<>)  
    | |  ---  
 0  | | Nil m_head  
    |  ---      
    | <alignment member> (size=7)  
 8  | m_head    
     --- 

  So, Tuple2<int *, Nil> has two m_head members -

  • it's own, and
  • one from the base class Tuple2<Nil>.

In contrast, the below shows that Tuple1<int*,Nil> has a single member x with an empty base class.

class Tuple1<int *,struct Nil>  size(8):
     ---  
 0  |  --- (base class Tuple1<struct Nil,struct Nil>)   
    |  --- 
 0  | x     
     ---
  • Related