Skip to content
sarah edited this page Jan 31, 2022 · 11 revisions
#include <veg/tuple.hpp>
namespace veg {
template <typename T, usize I>
using inner_ith = decltype(VEG_DECLVAL(T)[Fix<isize{I}>{}]);

template <typename... Ts>
struct Tuple { /*...*/ };
} // namespace veg

A lightweight tuple type with operator[] access notation and support for structured bindings (since C++17).

Example:

#include <veg/tuple.hpp>

using namespace veg;
auto main() -> int {
  Tuple<int, float> t{tuplify, 3, 2.5};
  dbg(t); // prints { 3, +2.5000e+00, }

  t[0_c] = 13;
  t[1_c] = 3.5F;
  dbg(t); // prints { 13, +3.5000e+00, }
}

Interface

Destructor

Tuple<Ts...>::~Tuple() = default;

Destroys the inner members


Constructors

Tuple constructor

Tuple<Ts...>::Tuple(Tuplify /*tuplify*/, Ts... args) noexcept(nothrow_movable<Ts> && ...);

template <typename... Fns>
Tuple<Ts...>::Tuple(InPlace<Tuplify> /*inplace[tuplify]*/, Fns... fns) noexcept(nothrow_fn_once<Fns, Ts> && ...)
requires(fn_once<Fns, Ts> && ...);

Builds a tuple from the provided arguments, or in place by calling the provided functions.


Copy constructor

explicit Tuple<Ts...>::Tuple(Tuple const&) = default;

Copies the contained values.


Move constructor

Tuple<Ts...>::Tuple(Tuple const&) = default;

Moves the contained values.


Assignment

Copy Assignment

auto Tuple<Ts...>::operator=(Tuple const&) -> Tuple& = default;

Copy assigns the contained values. Deleted for reference types.


Move Assignment

auto Tuple<Ts...>::operator=(Tuple&&) -> Tuple& = default;

Move assigns the contained values. Deleted for reference types.


Tuple Interface

As reference

auto Tuple<Ts...>::as_ref() const& noexcept -> Tuple<Ref<Ts>...>
auto Tuple<Ts...>::as_mut() & noexcept -> Tuple<Ref<Ts>...>

Returns a tuple of [mutable] references to the contained values.


Access

template <isize I>
auto Tuple<Ts...>::operator[](Fix<I>) const& noexcept -> Ts[I] const&
requires (I < sizeof...(Ts));

template <isize I>
auto Tuple<Ts...>::operator[](Fix<I>) & noexcept -> Ts[I]&
requires (I < sizeof...(Ts));

template <isize I>
auto Tuple<Ts...>::operator[](Fix<I>) && noexcept(nothrow_movable<Ts[I]>) -> Ts[I]
requires (I < sizeof...(Ts));

Returns the I-th value or a [mutable] reference to it.

Traits

Implements the fmt::Debug<Tuple<Ts...>> trait: prints the contained values. Implements the cmp::Ord<Tuple<Ts...>, Tuple<Us...>> trait and operator==: compares the contained values lexicographically.

Helper free functions

Tuplify

template <typename... Ts>
auto tuplify(Ts... args) noexcept(nothrow_movable<Ts> && ...) -> Tuple<Ts...>;

Returns a tuple of the given arguments.

Example:

#include <veg/tuple.hpp>
#include <veg/util/assert.hpp>

auto main() -> int {
  using namespace veg;
  Tuple<int, float> t0{tuplify, 1, 2.5F};
  auto t1 = tuplify(1, 2.5F);
  VEG_ASSERT(t0 == t1);
}

With

namespace tuple {
template <typename... Fns, typename... Ts = meta::invoke_result_t<Fns>...>
auto with(Fns... fns) noexcept(nothrow_fn_once<Fns, Ts> && ...) -> Tuple<Ts...>
requires (fn_once<Fns, Ts> && ...);
} // namespace tuple

Returns a tuple created by calling the given functions.

Example:

#include <veg/tuple.hpp>
#include <veg/util/assert.hpp>

auto main() -> int {
  using namespace veg;
  Tuple<int, float> t0{tuplify, 1, 2.5F};
  auto t1 = tuple::with(
    [](){ return 1; },
    [](){ return 2.5F; }
  );
  VEG_ASSERT(t0 == t1);
}

Zip

namespace tuple {
template <typename... Tuples>
auto zip(Tuples... tuples) noexcept(nothrow_movable<Tuples> && ...) -> Zip<Tuples>
requires (tuple<Tuples> && ...);
} // namespace tuple

tuple<Tuples> checks if the type is a tuple (inherits publicly and unambiguously from one Tuple<Ts...>).

Returns a tuple of tuples, whose Ith element is a tuple containing the Ith element from each parameter tuple.

Example:

#include <veg/tuple.hpp>
#include <veg/util/assert.hpp>

auto main() -> int {
  using namespace veg;
  auto t0 = tuplify(1, 2, 3);
  auto t1 = tuplify(4, 5, 6);

  auto t_zipped = tuple::zip(VEG_FWD(t0), VEG_FWD(t1));
  VEG_ASSERT(t_zipped == tuplify(
    tuplify(1, 4),
    tuplify(2, 5),
    tuplify(3, 6)
  ));
}

Cat

namespace tuple {
template <typename... Tuples>
auto cat(Tuples... tuples) noexcept(nothrow_movable<Tuples> && ...) -> Cat<Tuples>
requires (tuple<Tuples> && ...);
} // namespace tuple

tuple<Tuples> checks if the type is a tuple (inherits publicly and unambiguously from one Tuple<Ts...>).

Returns a tuple that contains the elements of the first tuple, followed by the elements of the second tuple, and so on.

Example:

#include <veg/tuple.hpp>
#include <veg/util/assert.hpp>

auto main() -> int {
  using namespace veg;
  auto t0 = tuplify(1, 2, 3);
  auto t1 = tuplify(4, 5, 6);

  auto t_catted = tuple::cat(VEG_FWD(t0), VEG_FWD(t1));
  VEG_ASSERT(t_catted == tuplify(1, 2, 3, 4, 5, 6));
}

Unpack

namespace tuple {
template <typename Fn, typename... Ts, typename Ret = meta::invoke_result_t<Fn, Ts...>>
auto unpack(Tuple<Ts...> tuple, Fn fn) noexcept(nothrow_fn_once<Fn, Ret, Ts...>)
requires fn_once<Fn, Ret, Ts...>;
} // namespace tuple

Returns VEG_FWD(fn)(VEG_FWD(tuple.0), VEG_FWD(tuple.1), ...).

Example:

#include <veg/tuple.hpp>
#include <veg/util/assert.hpp>

auto main() -> int {
  using namespace veg;
  auto t = tuplify(2, 5);
  VEG_ASSERT(tuple::unpack(
    VEG_FWD(t),
    [](auto a, auto b) { return a + b; }
  ) == 7);
}

For each

namespace tuple {
template <typename Fn, typename... Ts>
void for_each(Tuple<Ts...> tuple, Fn fn) noexcept(nothrow_fn_mut<Fn, void, Ts> && ...)
requires (fn_mut<Fn, void, Ts> && ...);

template <typename Fn, typename... Ts, usize... Is = [0, ..., sizeof...(Ts) - 1]>
void for_each_i(Tuple<Ts...> tuple, Fn fn) noexcept(nothrow_fn_once<inner_ith<Fn&, Is>, void, Ts> && ...)
requires (fn_once<inner_ith<Fn&, Is>, void, Ts> && ...);
} // namespace tuple

Call the provided function (or tuple-ish of functions) for each element in the tuple.

Example:

#include <veg/tuple.hpp>
#include <veg/util/assert.hpp>

auto main() -> int {
  using namespace veg;
  auto t = tuplify(1, 3.5);

  auto print = [](auto a) { dbg(a); };
  tuple::for_each(t.as_ref(), print);

  auto times_x_in_place = tuplify(
    [](RefMut<int> a) { (*a) *= 4; },
    [](RefMut<double> a) { (*a) *= 6; }
  );

  tuple::for_each_i(t.as_mut(), VEG_FWD(times_x_in_place));
  VEG_ASSERT(t == tuplify(4, 21));
}

Map

namespace tuple {
template <typename Fn, typename... Ts, typename... Rets = meta::invoke_result_t<Fn&, Ts>...>
auto map(Tuple<Ts...> tuple, Fn fn) noexcept(nothrow_fn_mut<Fn, void, Ts> && ...) -> Tuple<Rets...>
requires (fn_mut<Fn, Rets, Ts> && ...);

template <typename Fn, typename... Ts, usize... Is = [0, ..., sizeof...(Ts) - 1], typename... Rets = meta::invoke_result_t<inner_ith<Fn&, Is>, Ts>...>
void for_each_i(Tuple<Ts...> tuple, Fn fn) noexcept(nothrow_fn_once<inner_ith<Fn&, Is>, Rets, Ts> && ...)
requires (fn_once<inner_ith<Fn&, Is>, Rets, Ts> && ...);
} // namespace tuple

Call the provided function (or tuple-ish of functions) for each element in the tuple and get the result.

Example:

#include <veg/tuple.hpp>
#include <veg/util/assert.hpp>

auto main() -> int {
  using namespace veg;
  auto t = tuplify(1, 3.5);

  auto times_2 = [](auto a) { return 2 * (*a); };
  VEG_ASSERT(tuple::map(t.as_ref(), times_2) == tuplify(2, 7.0));

  auto times_x = tuplify(
    [](Ref<int> a) { return 4 * (*a); },
    [](Ref<double> a) { return 6 * (*a); }
  );

  VEG_ASSERT((tuple::map_i(t.as_ref(), VEG_FWD(times_x))) == tuplify(4, 21));
}