std::tuple::tuple(3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

πŸ’… NAME πŸ’…

std::tuple::tuple β€” bestie, it's the constructor of std::tuple, and it's lowkey the whole personality of the C++ Standard Library πŸ’–

(this page comes from the C++ Standard Library section, section 3, and it's giving "i contain multitudes" energy)


✨ SYNOPSIS ✨

Defined in header <tuple> πŸŽ€

(1)  constexpr tuple();

(since C++11) Β· (conditionally explicit)

(2)  tuple( const Types&... args );

(constexpr since C++14) Β· (conditionally explicit)

(3)  template< class... UTypes >
     tuple( UTypes&&... args );

(since C++11) Β· (constexpr since C++14) Β· (conditionally explicit)

(4)  template< class... UTypes >
     constexpr tuple( tuple<UTypes...>& other );

(since C++23) Β· (conditionally explicit)

(5)  template< class... UTypes >
     tuple( const tuple<UTypes...>& other );

(since C++11) Β· (constexpr since C++14) Β· (conditionally explicit)

(6)  template< class... UTypes >
     tuple( tuple<UTypes...>&& other );

(since C++11) Β· (constexpr since C++14) Β· (conditionally explicit)

(7)  template< class... UTypes >
     constexpr tuple( const tuple<UTypes...>&& other );

(since C++23) Β· (conditionally explicit)

(8)  template< class U1, class U2 >
     constexpr tuple( std::pair<U1, U2>& p );

(since C++23) Β· (conditionally explicit)

(9)  template< class U1, class U2 >
     tuple( const std::pair<U1, U2>& p );

(since C++11) Β· (constexpr since C++14) Β· (conditionally explicit)

(10) template< class U1, class U2 >
     tuple( std::pair<U1, U2>&& p );

(since C++11) Β· (constexpr since C++14) Β· (conditionally explicit)

(11) template< class U1, class U2 >
     constexpr tuple( const std::pair<U1, U2>&& p );

(since C++23) Β· (conditionally explicit)

(12) template< tuple-like UTuple >
     constexpr tuple( UTuple&& u );

(since C++23) Β· (conditionally explicit)

(13) tuple( const tuple& other ) = default;

(since C++11)

(14) tuple( tuple&& other ) = default;

(since C++11)

🌸 Allocator-extended constructors 🌸

(yes there are so many of them, and yes they all have an allocator moment)

(15) template< class Alloc >
     tuple( std::allocator_arg_t, const Alloc& a );

(since C++11) Β· (constexpr since C++20) Β· (conditionally explicit)

(16) template< class Alloc >
     tuple( std::allocator_arg_t, const Alloc& a,
            const Types&... args );

(since C++11) Β· (constexpr since C++20) Β· (conditionally explicit)

(17) template< class Alloc, class... UTypes >
     tuple( std::allocator_arg_t, const Alloc& a,
            UTypes&&... args );

(since C++11) Β· (constexpr since C++20) Β· (conditionally explicit)

(18) template< class Alloc, class... UTypes >
     constexpr tuple( std::allocator_arg_t, const Alloc& a,
                      tuple<UTypes...>& other );

(since C++23) Β· (conditionally explicit)

(19) template< class Alloc, class... UTypes >
     tuple( std::allocator_arg_t, const Alloc& a,
            const tuple<UTypes...>& other );

(since C++11) Β· (constexpr since C++20) Β· (conditionally explicit)

(20) template< class Alloc, class... UTypes >
     tuple( std::allocator_arg_t, const Alloc& a,
            tuple<UTypes...>&& other );

(since C++11) Β· (constexpr since C++20) Β· (conditionally explicit)

(21) template< class Alloc, class... UTypes >
     constexpr tuple( std::allocator_arg_t, const Alloc& a,
                      const tuple<UTypes...>&& other );

(since C++23) Β· (conditionally explicit)

(22) template< class Alloc, class U1, class U2 >
     constexpr tuple( std::allocator_arg_t, const Alloc& a,
                      std::pair<U1, U2>& p );

(since C++23) Β· (conditionally explicit)

(23) template< class Alloc, class U1, class U2 >
     tuple( std::allocator_arg_t, const Alloc& a,
            const std::pair<U1, U2>& p );

(since C++11) Β· (constexpr since C++20) Β· (conditionally explicit)

(24) template< class Alloc, class U1, class U2 >
     tuple( std::allocator_arg_t, const Alloc& a,
            std::pair<U1, U2>&& p );

(since C++11) Β· (constexpr since C++20) Β· (conditionally explicit)

(25) template< class Alloc, class U1, class U2 >
     constexpr tuple( std::allocator_arg_t, const Alloc& a,
                      const std::pair<U1, U2>&& p );

(since C++23) Β· (conditionally explicit)

(26) template< class Alloc, tuple-like UTuple >
     constexpr tuple( std::allocator_arg_t, const Alloc& a,
                      UTuple&& u );

(since C++23) Β· (conditionally explicit)

(27) template< class Alloc >
     tuple( std::allocator_arg_t, const Alloc& a,
            const tuple& other );

(since C++11) Β· (constexpr since C++20)

(28) template< class Alloc >
     tuple( std::allocator_arg_t, const Alloc& a,
            tuple&& other );

(since C++11) Β· (constexpr since C++20)


πŸŽ€ DESCRIPTION πŸŽ€

Constructs a new tuple. That's it, that's the vibe ✨ (okay no, there's so much more, buckle up bestie).

In the descriptions that follow, let

  • i be in the range [0, sizeof...(Types)) in order,
  • Ti be the ith type in Types, and
  • Ui be the ith type in a template parameter pack named UTypes,

where indexing is zero-based. (zero-based, always, no cap) 🫢


  1. Default constructor. Value-initializes all elements, if any. The default constructor is trivial if sizeof...(Types) == 0.
    • This overload participates in overload resolution only if std::is_default_constructible<Ti>::value is true for all i.
    • The constructor is explicit if and only if Ti is not copy-list-initializable from {} for at least one i.
  2. Direct constructor. Initializes each element of the tuple with the corresponding parameter.
    • This overload participates in overload resolution only if sizeof...(Types) >= 1 and std::is_copy_constructible<Ti>::value is true for all i.
    • This constructor is explicit if and only if std::is_convertible<const Ti&, Ti>::value is false for at least one i.
  3. Converting constructor. Initializes each element of the tuple with the corresponding value in std::forward<UTypes>(args).
    • This overload participates in overload resolution only if
      • sizeof...(Types) == sizeof...(UTypes),
      • sizeof...(Types) >= 1,
      • std::is_constructible<Ti, Ui>::value is true for all i, and
      • let D be std::decay<U0>::type (until C++20) std::remove_cvref_t<U0> (since C++20),
        • if sizeof...(Types) == 1, then D is not std::tuple, otherwise,
        • if sizeof...(Types) == 2 or sizeof...(Types) == 3, then either D is not std::allocator_arg_t, or T0 is std::allocator_arg_t.
    • The constructor is explicit if and only if std::is_convertible<Ui, Ti>::value is false for at least one i.
    • This constructor is defined as deleted if the initialization of any element that is a reference would bind it to a temporary object. (since C++23) (!! dangling reference behavior, we do NOT stan, it's literally deleted for a reason bestie)
  4. 4-7) Converting constructor. Initializes each element of the tuple with the corresponding element of other.

Formally, let FWD(other) be std::forward<decltype(other)>(other), for all i, initializes ith element of the tuple with std::get<i>(FWD(other)).

  • This overload participates in overload resolution only if
    • sizeof...(Types) == sizeof...(UTypes),
    • std::is_constructible_v<Ti, decltype(std::get<i>(FWD(other)))> is true for all i, and
    • either
      • sizeof...(Types) is not 1, or
      • (when Types... expands to T and UTypes... expands to U) std::is_convertible_v<decltype(other), T>, std::is_constructible_v<T, decltype(other)>, and std::is_same_v<T, U> are all false.
  • These constructors are explicit if and only if std::is_convertible_v<decltype(std::get<i>(FWD(other))), Ti> is false for at least one i.
  • These constructors are defined as deleted if the initialization of any element that is a reference would bind it to a temporary object. (since C++23) πŸ’€
  1. 8-11) Pair constructor. Constructs a 2-element tuple with each element constructed from the corresponding element of p.

Formally, let FWD(p) be std::forward<decltype(p)>(p), initializes the first element with std::get<0>(FWD(p)) and the second element with std::get<1>(FWD(p)). πŸ“

  • this overload only shows up in overload resolution if all of these are true:
    • sizeof...(Types) == 2,
    • std::is_constructible_v<T0, decltype(std::get<0>(FWD(p)))> is true, and
    • std::is_constructible_v<T1, decltype(std::get<1>(FWD(p)))> is true.
  • it's explicit if and only if std::is_convertible_v<decltype(std::get<0>(FWD(p))), T0> or std::is_convertible_v<decltype(std::get<1>(FWD(p))), T1> is false.
  • these constructors are defined as deleted if initializing any element that's a reference would bind it to a temporary object (since C++23).

12) the tuple-like constructor ✨ it builds a tuple where each element comes from the matching element of u.

Formally, for every i, it initializes the ith element of the tuple with std::get<i>(std::forward<UTuple>(u)).

  • this overload only participates in overload resolution if all of this holds:
    • std::same_as<std::remove_cvref_t<UTuple>, std::tuple> is false,
    • std::remove_cvref_t<UTuple> is not a specialization of std::ranges::subrange,
    • sizeof...(Types) equals std::tuple_size_v<std::remove_cvref_t<UTuple>>,
    • std::is_constructible_v<Ti, decltype(std::get<i>(std::forward<UTuple>(u)))> is true for all i, and
    • either:
      • sizeof...(Types) is not 1, or
      • (when Types... expands to T) std::is_convertible_v<UTuple, T> and std::is_constructible_v<T, UTuple> are both false.
  • this constructor is defined as deleted if initializing any element that's a reference would bind it to a temporary object πŸ’€

13) the implicitly-defined copy constructor. It initializes every element of the tuple with the matching element of other.

  • it's constexpr if every operation it does is constexpr. For the empty tuple, std::tuple<>, it is constexpr πŸŽ€
  • std::is_copy_constructible<Ti>::value must be true for all i β€” otherwise:
    • the behavior is undefined (until C++20)
    • the program is ill-formed (since C++20)

14) the implicitly-defined move constructor. For every i, it initializes the ith element of the tuple with std::forward<Ui>(std::get<i>(other)).

  • it's constexpr if every operation it does is constexpr. For the empty tuple, std::tuple<>, it is constexpr ✨
  • std::is_move_constructible<Ti>::value must be true for all i β€” otherwise:
    • the behavior is undefined (until C++20)
    • this overload does not participate in overload resolution (since C++20)

15-28) exactly the same as (1-14), except each element is created with uses-allocator construction β€” meaning the Allocator object a gets passed as an extra argument to the constructor of every element for which std::uses_allocator<Ui, Alloc>::value is true. no cap, it's the same vibe just with an allocator along for the ride 🫢

πŸŽ€ PARAMETERS

  • args β€” values used to initialize each element of the tuple
  • other β€” the tuple of values used to initialize each element of the tuple
  • p β€” the pair of values used to initialize both elements of the 2-tuple
  • u β€” the tuple-like object of values used to initialize each element of the tuple
  • a β€” the allocator to use in uses-allocator construction

πŸ’­ NOTES

Conditionally-explicit constructors are the moment β€” they let you construct a tuple in a copy-initialization context using list-initialization syntax:

std::tuple<int, int> foo_tuple()
{
    // return {1, -1};             // Error before N4387
    return std::make_tuple(1, -1); // Always works
}

heads up bestie β€” if any element of the list isn't implicitly convertible to the corresponding element of the target tuple, the constructors become explicit:

using namespace std::chrono;
void launch_rocket_at(std::tuple<hours, minutes, seconds>);

launch_rocket_at({hours(1), minutes(2), seconds(3)}); // OK
launch_rocket_at({1, 2, 3}); // Error: int is not implicitly convertible to duration
launch_rocket_at(std::tuple<hours, minutes, seconds>{1, 2, 3}); // OK

πŸ“ EXAMPLE

// Run this code

#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <vector>

// helper function to print a vector to a stream
template<class Os, class T>
Os& operator<<(Os& os, std::vector<T> const& v)
{
    os << '{';
    for (auto i{v.size()}; const T& e : v)
        os << e << (--i ? "," : "");
    return os << '}';
}

template<class T>
void print_single(T const& v)
{
    if constexpr (std::is_same_v<T, std::decay_t<std::string>>)
        std::cout << std::quoted(v);
    else if constexpr (std::is_same_v<std::decay_t<T>, char>)
        std::cout << "'" << v << "'";
    else
        std::cout << v;
}

// helper function to print a tuple of any size
template<class Tuple, std::size_t N>
struct TuplePrinter
{
    static void print(const Tuple& t)
    {
        TuplePrinter<Tuple, N - 1>::print(t);
        std::cout << ", ";
        print_single(std::get<N - 1>(t));
    }
};

template<class Tuple>
struct TuplePrinter<Tuple, 1>
{
    static void print(const Tuple& t)
    {
        print_single(std::get<0>(t));
    }
};

template<class... Args>
void print(std::string_view message, const std::tuple<Args...>& t)
{
    std::cout << message << " (";
    TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
    std::cout << ")\n";
}
// end helper function

int main()
{
    std::tuple<int, std::string, double> t1;
    print("Value-initialized, t1:", t1);

    std::tuple<int, std::string, double> t2{42, "Test", -3.14};
    print("Initialized with values, t2:", t2);

    std::tuple<char, std::string, int> t3{t2};
    print("Implicitly converted, t3:", t3);

    std::tuple<int, double> t4{std::make_pair(42, 3.14)};
    print("Constructed from a pair, t4:", t4);

    // given Allocator my_alloc with a single-argument constructor
    // my_alloc(int); use my_alloc(1) to allocate 5 ints in a vector
    using my_alloc = std::allocator<int>;
    std::vector<int, my_alloc> v{5, 1, my_alloc{/* 1 */}};

    // use my_alloc(2) to allocate 5 ints in a vector in a tuple
    std::tuple<int, std::vector<int, my_alloc>, double> t5
        {std::allocator_arg, my_alloc{/* 2 */}, 42, v, -3.14};
    print("Constructed with allocator, t5:", t5);
}

Possible output:

Value-initialized, t1: (0, "", 0)
Initialized with values, t2: (42, "Test", -3.14)
Implicitly converted, t3: ('*', "Test", -3)
Constructed from a pair, t4: (42, 3.14)
Constructed with allocator, t5: (42, {1,1,1,1,1}, -3.14)

🩹 DEFECT REPORTS

These are the behavior-changing defect reports that got applied retroactively to already-published C++ standards (yes, time travel is real in the committee πŸ’«):

  • LWG 2510 Β· applied to C++11 Β· as published: the default constructor was implicit β†’ made conditionally-explicit
  • LWG 3121 Β· applied to C++11 Β· as published: the constructor of 1-tuple might recursively check the constraints; the allocator_arg_t argument brought ambiguity β†’ furtherly constrained the constructor
  • LWG 3158 Β· applied to C++11 Β· as published: the uses-allocator constructor corresponding to the default constructor was implicit β†’ made conditionally-explicit
  • LWG 3211 Β· applied to C++11 Β· as published: whether the default constructor of tuple<> is trivial was unspecified β†’ require to be trivial
  • N4387 Β· applied to C++11 Β· as published: some constructors were explicit, preventing useful behavior β†’ most constructors made conditionally-explicit
  • LWG 4045 Β· applied to C++23 Β· as published: the tuple-like constructor may potentially create dangling references β†’ made defined as deleted

πŸ‘€ SEE ALSO

  • operator= β€” assigns the contents of one tuple to another (public member function)
  • make_tuple (C++11) β€” creates a tuple object of the type defined by the argument types (function template)
  • tie (C++11) β€” creates a tuple of lvalue references or unpacks a tuple into individual objects (function template)
  • forward_as_tuple (C++11) β€” creates a tuple of forwarding references (function template)
  • constructor β€” constructs new pair (public member function of std::pair<T1,T2>)

okay that's the whole page bestie, you ate that πŸ’…βœ¨ this one's straight from cppreference.com, dated 2024.06.10 β€” go forth and construct your tuples with main character energy πŸ‘‘πŸ¦‹πŸ’–

πŸ“¦ where this page comes from