The storage design of std::tuple relies on a recursive inheritance model. A tuple with N elements (where N > 0) is implemented as a derived class that privately inherits from a base class representing a tuple of the remaining N-1 elements. The terminal case is an empty tuple specialization for zero elements. Consider the following instantiation:
std::tuple<char, float, std::string> sample('a', 2.5f, "text");
Recursive construction proceeds as follows:
- The outermost layer prepares to store the first element 'a', with the remainder being a
std::tuple<float, std::string>. - The next layer stores the second element 2.5f, leaving a
std::tuple<std::string>. - The third layer stores "text", leaving an empty
std::tuple<>, which terminates the recursion. - Elements are actually stored during recursion unwinding: "text" is stored first, followed by 2.5f, and finally 'a'.
This results in a last-in, first-out construction order. The implementation uses private inheritence to maintain encapsulation and enable empty base optimization, minimizing object size.
Key implementation components include:
- A member
_Myfirstof type_Tuple_val<_This>stores the first element. - The base class
_Mybaserepresents the tuple of remaining elements. _Tuple_valis a wrapper that holds the actual value in its_Valmember.
Example inheritance hierarchy for the tuple above:
class std::tuple<>;
class std::tuple<std::string>;
class std::tuple<float, std::string>;
class std::tuple<char, float, std::string>;
Memory layout reflects this hierarchy, with each derived class containing its _Myfirst member plus the base subobject.
Construction Details
Constructors initialize _Myfirst and delegate to the base class constructor recursive. Tag dispatch diffferentiates between construction from individual arguments versus unpacking another tuple. Example tags:
struct _Exact_args_t {};
struct _Unpack_tuple_t {};
A typical forwarding constructor:
template <class _Tag, class _First, class... _Rem>
constexpr tuple(_Tag, _First&& arg1, _Rem&&... args)
: _Mybase(_Exact_args_t{}, std::forward<_Rem>(args)...),
_Myfirst(std::forward<_First>(arg1)) {}
Default copy and move constructors are provided, while volatile copy assignment is deleted.
Tuple Size
std::tuple_size is specialized for tuples to compute the number of elements via sizeof...:
template <class... Args>
struct tuple_size<std::tuple<Args...>>
: std::integral_constant<std::size_t, sizeof...(Args)> {};
Element Access
std::tuple_element retrieves the type at a given index through recursive template specialization:
template <std::size_t I, class Head, class... Tail>
struct tuple_element<I, std::tuple<Head, Tail...>>
: tuple_element<I-1, std::tuple<Tail...>> {};
template <class Head, class... Tail>
struct tuple_element<0, std::tuple<Head, Tail...>> {
using type = Head;
};
std::get utilizes tuple_element to access the corresponding _Myfirst._Val member after static casting to the appropriate base class. Overloads support const, non-const, lvalue, and rvalue references.
Assignment Operators
Assignment operators are conditionally enabled based on the assignability of contained types. They assign _Myfirst._Val and recursively assign the base subobject via _Get_rest().
Equality Comparison
The operator== uses a recursive _Equals member function that compares corresponding elements with ==. A static assertion ensures tuples have equal sizes; incompatible types result in compilation errors.
template <class... Other>
constexpr bool _Equals(const std::tuple<Other...>& other) const {
return _Myfirst._Val == other._Myfirst._Val
&& _Mybase::_Equals(other._Get_rest());
}