tuple.hpp
provides a tuple implementation that mirrors
std::tuple. Mostly,
stdx::tuple works the same way as std::tuple, with some extra functionality
and faster compilation times. All functions on tuples are constexpr-capable.
|
Note
|
tuple is available only in C++20 and later.
|
As with std::tuple, we can ask for the compile-time elements and size of a
stdx::tuple using tuple_element_t and tuple_size_v.
template <typename T>
constexpr auto tuple_size_v = /* implementation */;
template <std::size_t I, typename T>
using tuple_element_t = /* implementation */;The standard provides std::tuple_element and std::tuple_size as types; in
stdx, only tuple_element_t and tuple_size_v are provided, since they are
the most frequently used constructs. If needed, types can be synthesized from
them, but if not, it’s quicker to compile just an alias and a variable template.
A tuple can be constructed either with CTAD, or with make_tuple, or with
forward_as_tuple.
auto t = stdx::tuple{1, 2}; // tuple<int, int>
auto t = stdx::make_tuple(1, 2); // the same
auto t = stdx::forward_as_tuple(1, 2); // tuple<int&&, int&&>make_tuple decays its arguments, just like std::make_tuple. However,
std::make_tuple also does std::reference_wrapper-unwrapping, which
stdx::make_tuple does not.
forward_as_tuple creates a tuple of forwarded references, like
std::forward_as_tuple.
The ordinary way to access tuple elements is to use get, as with a std::tuple:
auto t = stdx::tuple{1, true}; // tuple<int, bool>
auto x_by_index = get<0>(t); // int
auto y_by_index = get<1>(t); // bool
auto x_by_type = get<int>(t); // int
auto y_by_type = get<bool>(t); // bool|
Note
|
stdx::get is accessed here by argument-dependent lookup.
|
Like std::get, stdx::get with a type will be ambiguous if that type is in
the tuple multiple times.
Unlike std::get, stdx::get is "SFINAE-friendly". If stdx::get causes a
function template instantiation to be ill-formed, it will not cause an error,
but instead will cause that function to be silently removed from the overload
set candidates. With stdx::get, Substitution Failure Is Not An Error.
For access by index, we can also use indexing syntax:
using namespace stdx::literals;
auto t = stdx::tuple{1, true};
auto x_by_index1 = t[index<0>];
auto x_by_index2 = t[0_idx]; // equivalent_idx is a user-defined literal in the stdx::literals namespace. It is
equivalent to using the index variable template.
|
Note
|
All forms of access preserve the value category of the tuple; i.e.
accessing an int member of a stdx::tuple const & gives an int const & and
so on.
|
Comparing one stdx::tuple with another works
the same way as
with std::tuple.
apply can be used like
std::apply, but is a member
function.
auto t = stdx::tuple{1, 2};
auto sum = t.apply([] (auto... args) { return (args + ... + 0); }); // 3apply is also available as a free function in
tuple_algorithms.hpp.
fold_left and fold_right can be used to fold over a tuple and compute a
reduction. They both take an initial value for the fold as the first argument,
and a binary reduction function as the second.
auto t = stdx::tuple{1, 2, 3};
auto sum1 = t.fold_left(0, std::plus{}); // 6
auto sum2 = t.fold_right(0, std::plus{}); // also 6Here, fold_left computes a left-fold, i.e. (((0 + 1) + 2) + 3). fold_right
computes the right-fold (0 + (1 + (2 + 3))).
Both forms of fold can perform computations in type-space as well as value-space. That is, the return type of the binary function passed to the fold can depend on the arguments. In this way a fold can build up a type dependent on what is in the tuple.
auto t1 = stdx::tuple{1, 2, 3};
auto t2 = t.fold_left(
stdx::tuple{},
[] (auto so_far, auto y) { return tuple_cat(so_far, stdx::tuple{y}); });
// t1 and t2 are the same, but intermediate types in the fold varied|
Note
|
tuple_cat is an algorithm in
tuple_algorithms.hpp.
|
join is a member function that works the same way as fold_left, but without
needing an initial value. It has overloads either for non-empty tuples, or for
empty tuples with a given default.
auto sum1 = stdx::tuple{1, 2, 3}.join(std::plus{}); // 6
auto sum2 = stdx::tuple{1}.join(std::plus{}); // 1
auto sum3 = stdx::tuple{}.join(42, std::plus{}); // 42join is useful for things like string concatenation with comma separation.
auto sum1 = stdx::tuple{"hello"s, "world"s}.join(
[] (auto const &acc, auto const &s) { return acc + ", " + s; }); // "hello, world"Sometimes, it is useful to index a tuple by something other than a plain
std::size_t or a type. A tuple can act as a sort of map by indexing on
multiple types, for instance. That’s the job of indexed_tuple.
template <typename Key, typename Value> struct map_entry {
using key_t = Key;
using value_t = Value;
value_t value;
};
template <typename T> using key_for = typename T::key_t;
struct X;
struct Y;
auto t = stdx::make_indexed_tuple<key_for>(map_entry<X, int>{42},
map_entry<Y, int>{17});
auto x = get<X>(t).value; // 42
auto y = get<Y>(t).value; // 17Notice a few things here:
-
XandYare tag types; declared only and not defined. -
make_indexed_tupletakes a number of type functions (here justkey_for) that define how to look up elements. -
getis working not with astd::size_tindex or the actual type contained within the tuple, but with the tag type that will be found bykey_for.
A regular (unindexed) tuple can be converted to an indexed_tuple using
apply_indices to add type-indexing functions:
// with definitions as above
auto t = stdx::tuple{map_entry<X, int>{42}}; // regular tuple
auto i = stdx::apply_indices<key_for>(t); // tuple indexed with key_for
auto x = get<X>(i).value; // 42