Class Subspace :: sus :: tuple_type :: Tuple

template <class T, class... Ts>
class Tuple final
{ ... };

A Tuple is a finite sequence of one or more heterogeneous values.

The Tuple is similar to std::tuple with some differences:

  • It allows storing reference types.
  • It interacts with iterators through satisfying Extend, allowing an iterator to unzip() into a Tuple of collections that satisfy Extend.
  • It provides explicit methods for const, mutable or rvalue access to its values.
  • It satisfies Clone if its elements all satisfy Clone.

Tuple elements can also be accessed through get() for code that wants to work generically over tuple-like objects including sus::Tuple and std::tuple.

Tail padding

The Tuple's tail padding may be reused when the Tuple is marked as [[no_unique_address]]. The Tuple will have tail padding if the first type has a size that is not a multiple of the Tuple's alignment. For example if it's smaller than the alignment, such as Tuple<u8, u64> which has (alignof(u64) == sizeof(u64)) - sizeof(u8) or 7 bytes of tail padding.

struct S {
  [[no_unique_address]] Tuple<u32, u64> tuple;  // 16 bytes.
  u32 val;  // 4 bytes.
};  // 16 bytes, since `val` is stored inside `tuple`.

However note that this behaviour is compiler-dependent, and MSVC does not use the [[no_unique_address]] hint.

Use sus::data_size_of<T>() to determine the size of T excluding its tail padding (so sus::size_of<T>() - sus::data_size_of<T>() is the tail padding), which can be useful to ensure you have the expected behaviour from your types.

Additionally types within the tuple may be placed inside the tail padding of other types in the tuple, should such padding exist.

Generally, but not always, use of tail padding in Tuple is optimized by ordering types (left-to-right in the template variables) from smallest-to- largest for simple types such as integers (which have no tail padding themselves), or in least-to-most tail-padding for more complex types. Elements in a Tuple are stored internally in reverse of the order they are specified, which is why the size of the first element matters for the Tuple's externally usable tail padding.

Static Methods

Tuple()
requires
!((std::is_trivially_default_constructible_v<T> && ... &&
                std::is_trivially_default_constructible_v<Ts>) &&
              !(std::is_reference_v<T> || ... || std::is_reference_v<Ts>)
             )
(::sus::construct::Default<T> && ... &&
              ::sus::construct::Default<Ts>)
Tuple()
requires
(std::is_trivially_default_constructible_v<T> && ... &&
              std::is_trivially_default_constructible_v<Ts>)
!(std::is_reference_v<T> || ... || std::is_reference_v<Ts>)

Construct a Tuple with the default value for the types it contains.

The Tuple's contained types must all be #Default, and will be constructed through that trait.

template <class U, class... Us>
Tuple(U&& first, Us&&... more)
requires
std::convertible_to<U, T>
std::convertible_to<Ts>...
sizeof...(Us) == sizeof...(Ts)
(sus::construct::SafelyConstructibleFromReference<T, U &&> &&
              ... &&
              sus::construct::SafelyConstructibleFromReference<Ts, Us &&>)

Construct a Tuple with the given values.

Const References

For Result<const T&, E> it is possible to bind to a temporary which would create a memory safety bug. The [[clang::lifetimebound]] attribute is used to prevent this via Clang. But additionally, the incoming type is required to match with sus::construct::SafelyConstructibleFromReference to prevent conversions that would construct a temporary.

To force accepting a const reference anyway in cases where a type can convert to a reference without constructing a temporary, use an unsafe static_cast<const T&>() at the callsite (and document it =)).

template <class U, class... Us>
Tuple(const Tuple<U, Us...>& tuple)
requires
(std::convertible_to<const U&, T> && ... &&
         std::convertible_to<const Us&, Ts>)
sizeof...(Ts) == sizeof...(Us)
(::sus::construct::SafelyConstructibleFromReference<T, const U&> &&
         ... &&
         ::sus::construct::SafelyConstructibleFromReference<Ts, const Us&>)
template <class U, class... Us>
Tuple(Tuple<U, Us...>&& tuple)
requires
(std::convertible_to<U &&, T> && ... &&
              std::convertible_to<Us &&, Ts>)
sizeof...(Ts) == sizeof...(Us)
(::sus::construct::SafelyConstructibleFromReference<T, U &&> &&
              ... &&
              ::sus::construct::SafelyConstructibleFromReference<Ts, Us &&>)

Converts from Tuple<X', Y', Z'> to Tuple<X, Y, Z> when X', Y', and Z' can be converted to X, Y, and Z.

Methods

template <size_t I>
auto at() const& -> const auto&
requires
I <= sizeof...(Ts)
template <size_t I>
auto at() && -> const auto&
deleted

Gets a const reference to the Ith element in the tuple.

template <size_t I>
auto at_mut() & -> auto&
requires
I <= sizeof...(Ts)

Gets a mutable reference to the Ith element in the tuple.

auto clone() const& -> Tuple<T, Ts...>
requires
(::sus::mem::CloneOrRef<T> && ... && ::sus::mem::CloneOrRef<Ts>)
!(::sus::mem::CopyOrRef<T> && ... && ::sus::mem::CopyOrRef<Ts>)

sus::mem::Clone trait.

template <class U, class... Us>
auto extend(IntoIterator<Tuple<U, Us...>> auto&& ii) -> void
requires
sizeof...(Us) == sizeof...(Ts)
(::sus::iter::Extend<T, U> && ... && ::sus::iter::Extend<Ts, Us>)
sus::mem::IsMoveRef<decltype(ii)>

Satisfies sus::iter::Extend for a Tuple of collections that each satisfies Extend for its position-relative type in the iterator of tuples.

The tuple this is called on is a set of collections. The iterable passed in as an argument yields tuples of items that will be appended to the collections.

The item types in the argument can not be deduced, so they must be explicitly specified by the caller, such as:

collections.extend<i32, std::string>(iter_over_tuples_i32_string());

Allows to extend a tuple of collections that also implement Extend.

See also: IteratorBase::unzip.

template <size_t I>
auto into_inner() && -> decltype(auto)
requires
I <= sizeof...(Ts)

Removes the Ith element from the tuple, leaving the Tuple in a moved-from state where it should no longer be used.

Operators

auto operator<=>(const Tuple<T, Ts...>& r) const& -> std::strong_ordering
requires
(::sus::cmp::StrongOrd<T> && ... && ::sus::cmp::StrongOrd<Ts>)
template <class U, class... Us>
auto operator<=>(const Tuple<U, Us...>& r) const& -> std::strong_ordering
requires
sizeof...(Us) == sizeof...(Ts)
(::sus::cmp::StrongOrd<T, U> && ... &&
              ::sus::cmp::StrongOrd<Ts, Us>)
auto operator<=>(const Tuple<T, Ts...>& r) const& -> std::weak_ordering
requires
!(::sus::cmp::StrongOrd<T> && ... && ::sus::cmp::StrongOrd<Ts>)
(::sus::cmp::Ord<T> && ... && ::sus::cmp::Ord<Ts>)
template <class U, class... Us>
auto operator<=>(const Tuple<U, Us...>& r) const& -> std::weak_ordering
requires
sizeof...(Us) == sizeof...(Ts)
!(::sus::cmp::StrongOrd<T, U> && ... &&
               ::sus::cmp::StrongOrd<Ts, Us>)
(::sus::cmp::Ord<T, U> && ... && ::sus::cmp::Ord<Ts, Us>)
auto operator<=>(const Tuple<T, Ts...>& r) const& -> std::partial_ordering
requires
!(::sus::cmp::Ord<T> && ... && ::sus::cmp::Ord<Ts>)
(::sus::cmp::PartialOrd<T> && ... && ::sus::cmp::PartialOrd<Ts>)
template <class U, class... Us>
auto operator<=>(const Tuple<U, Us...>& r) const& -> std::partial_ordering
requires
sizeof...(Us) == sizeof...(Ts)
!(::sus::cmp::Ord<T, U> && ... && ::sus::cmp::Ord<Ts, Us>)
(::sus::cmp::PartialOrd<T, U> && ... &&
              ::sus::cmp::PartialOrd<Ts, Us>)

Compares two Tuples.

Satisfies sus::cmp::StrongOrd<Tuple<...>> if sus::cmp::StrongOrd<...>. Satisfies sus::cmp::Ord<Tuple<...>> if sus::cmp::Ord<...>. Satisfies sus::cmp::PartialOrd<Tuple<...>> if sus::cmp::PartialOrd<...>.

The non-template overloads allow tuple() marker types to convert to Tuple for comparison.

auto operator==(const Tuple<T, Ts...>& r) const& -> bool
requires
(::sus::cmp::Eq<T> && ... && ::sus::cmp::Eq<Ts>)
template <class U, class... Us>
auto operator==(const Tuple<U, Us...>& r) const& -> bool
requires
sizeof...(Us) == sizeof...(Ts)
(::sus::cmp::Eq<T, U> && ... && ::sus::cmp::Eq<Ts, Us>)

Satisfies the Eq concept if the types inside satisfy Eq.

Implementation Note

The non-template overload allows tuple() marker types to convert to Tuple for comparison.