std::tuple_cat(3)

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

πŸ’… std::tuple_cat β€” the C++ Standard Library glow-up πŸŽ€

okay so bestie, welcome to std::tuple_cat(3) from the C++ Standard Library β€” yes that's the manual page name, we're being fancy today. Sit down, grab a strawberry drink πŸ“, and let me tell you about the function that literally just glues tuples together. it's giving "group chat merge" energy and honestly? iconic.


✨ SYNOPSIS ✨

Defined in header <tuple>

template< class... Tuples >
(since C++11)
std::tuple</* CTypes */...> tuple_cat( Tuples&&... args );
(until C++14)

template< class... Tuples >
(since C++14)
constexpr std::tuple</* CTypes */...> tuple_cat( Tuples&&... args );
(until C++23)

template< tuple-like... Tuples >
(since C++23)
constexpr std::tuple</* CTypes */...> tuple_cat( Tuples&&... args );

periodt. that's the whole signature family tree. notice how she got constexpr in C++14 and then got re-constrained to tuple-like... Tuples in C++23 β€” main character evolution, honestly. πŸ’…


🌸 DESCRIPTION 🌸

So what does she do? She constructs a tuple that is a concatenation of all tuples in args. that's it. that's the vibe. you hand her a bunch of tuples and she hands you back one big tuple. 🫢

The element types /* CTypes */ of the returned tuple are formed by concatenating the elements type packs of all

  • std::tuple (until C++23)
  • tuple-like (since C++23)

types in Tuples, in order. no cap, the order matters β€” first tuple's elements first, then the next, etc. πŸ¦‹

⚠️ okay important caveat time, and this one's serious: the behavior is undefined if any type in std::decay_t<Tuples>... is not a specialization of std::tuple.

However, (until C++23) an implementation may choose to support types (such as std::array and std::pair) that follow the tuple-like protocol. so like... she might be chill about it, but don't count on it, that's implementation-dependent πŸ™ˆ

(since C++23) the types std::decay_t<Tuples>... are constrained to be tuple-like, i.e. each type therein is required to be a specialization of std::tuple or another type (such as std::array and std::pair) that models tuple-like. so from C++23 onwards this isn't vibes-based anymore, it's law. πŸ‘‘

and one more, because detail is everything:

  • If any type in /* CTypes */ is not constructible from the type of the corresponding element in the sequence of elements concatenated from args:
    • the behavior is undefined (until C++23)
    • the program is ill-formed (since C++23)

like, the standard said "we're not doing undefined behaviour for this anymore, we're just gonna refuse to compile." honestly? fair. πŸ’€


🧚 PARAMETERS 🧚

  • args β€” zero or more tuples to concatenate. yes, zero is allowed bestie. she's flexible like that. ✨

πŸŽ€ RETURN VALUE πŸŽ€

A std::tuple object composed of all elements of all argument tuples, constructed from

std::get<j>(std::forward<Ti>(arg))

for each individual element. that's the whole secret sauce right there β€” she's doing std::get + perfect forwarding per element and stitching them into a fresh tuple. iykyk. 🌈


πŸ’– EXAMPLE πŸ’–

// Run this code

#include <iostream>
#include <string>
#include <tuple>

// 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 << ", " << std::get<N-1>(t);
    }
};

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

template<typename... Args, std::enable_if_t<sizeof...(Args) == 0, int> = 0>
void print(const std::tuple<Args...>& t)
{
    std::cout << "()\n";
}

template<typename... Args, std::enable_if_t<sizeof...(Args) != 0, int> = 0>
void print(const std::tuple<Args...>& t)
{
    std::cout << "(";
    TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
    std::cout << ")\n";
}
// end helper function

int main()
{
    std::tuple<int, std::string, float> t1(10, "Test", 3.14);
    int n = 7;
    auto t2 = std::tuple_cat(t1, std::make_tuple("Foo", "bar"), t1, std::tie(n));
    n = 42;
    print(t2);
}

btw the TuplePrinter struct recursion is genuinely such a serve, she's printing tuples of any size with templates. that's not beginner behaviour, that's icon behaviour. πŸ”₯


⭐ OUTPUT ⭐

(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 42)

look at her!! πŸ₯Ή and notice the last value is 42, not 7 β€” because std::tie(n) holds a reference to n, and n was changed to 42 before printing. the tuple was built, but the reference is still live. chef's kiss. πŸ’œ


πŸ¦‹ SEE ALSO πŸ¦‹

  • make_tuple β€” creates a tuple object of the type defined by the argument types (C++11) (function template)
  • tie β€” creates a tuple of lvalue references or unpacks a tuple into individual objects (C++11) (function template)
  • forward_as_tuple β€” creates a tuple of forwarding references (C++11) (function template)

okay that's the page!! πŸŽ€ big thank you to cppreference.com, version dated 2024.06.10, and to std::tuple_cat(3) for being the concatenation queen we didn't deserve. go forth and merge your tuples bestie, and remember: the behavior is undefined if you don't read the caveats. love u, bye!! πŸ’…βœ¨πŸ«Ά

πŸ“¦ where this page comes from