Skip to content

Instantly share code, notes, and snippets.

@willwray
Last active June 26, 2023 17:32
Show Gist options
  • Save willwray/32ea631b5b6d59a0c9d80573209560e7 to your computer and use it in GitHub Desktop.
Save willwray/32ea631b5b6d59a0c9d80573209560e7 to your computer and use it in GitHub Desktop.
Amalgamated header for tupl library
/*
SPDX-FileCopyrightText: 2023 The Lemuriad <opensource@lemurianlabs.com>
SPDX-License-Identifier: BSL-1.0
Repo: https://github.com/Lemuriad/tupl
tupl is a C++20 type implemented as a rule-of-zero aggregate struct.
Tested on GCC, Clang and recent MSVC.
tupl_amalgam.hpp is a single header amalgamated from tupl #include's
WARNING: Do not edit this file "tupl_amalgam.hpp"
=======
* It's a generated file, overwritten whenever the generator is rerun.
***********************************************************************/
#ifndef LML_TUPL_HPP
#define LML_TUPL_HPP
#include <compare>
#include <concepts>
#include <cstdint>
#define UINTPTR uintptr_t
// UNREACHABLE: platform-specific annotation for unreachable code
// till there's a portable or standardized annotation (c.f. P2390 P0627)
#ifndef UNREACHABLE
# ifdef __GNUC__
# define UNREACHABLE() __builtin_unreachable();
# elif defined(_MSC_VER)
# define UNREACHABLE() __assume(false);
# else
# define UNREACHABLE() // empty def; there may be errors or warnings
# endif
#endif
// NUA no_unique_address attribute, platform specific handling for MSVC
#ifndef NUA
# ifdef __has_cpp_attribute
# if __has_cpp_attribute(no_unique_address)
# define NUA [[no_unique_address]]
# endif
# endif
#endif
//
// no_unique_address: platform workaround for MSVC >= VS2019 v16.10
// (_MSC_FULL_VER >= 192829913 for VS2019 v16.9)
#if !defined(NUA) && _MSC_VER >= 1929
# define NUA [[msvc::no_unique_address]]
#endif
//
#if !defined(NUA)
static_assert(false,"[[no_unique_address]] attribute is required");
#endif
// INT_SEQ_MAP & SEQ_MAP wrap integer-sequence generator builtins
// moved to index_sequences.hpp
// type_pack_element_t; Clang has a builtin, use it if available
//
#ifdef __has_builtin
#if __has_builtin(__type_pack_element)
#define TYPEPACKEL __type_pack_element<I,E...> // I,E... are 'baked in'
#endif
#endif
// Supress noisy missing-braces warnings on Clang
//
#ifndef NO_WARN_MISSING_BRACES
# if defined(__clang__)
# define NO_WARN_MISSING_BRACES(...)\
_Pragma("GCC diagnostic push")\
_Pragma("GCC diagnostic ignored \"-Wmissing-braces\"")\
__VA_ARGS__ \
_Pragma("GCC diagnostic pop")
# else
# define NO_WARN_MISSING_BRACES(...)__VA_ARGS__
# endif
#endif
#ifndef NAMESPACE_ID /* Configure your possibly-nested namespace-id */
# define NAMESPACE_ID lml
#endif
#ifndef NAMESPACE_ID_IS_EMPTY
# define NAMESPACE_ID_IS_EMPTY_(ID,ONE)ID##ONE
# define NAMESPACE_ID_IS_EMPTY(NS_ID)NAMESPACE_ID_IS_EMPTY_(NS_ID,0x01)
# if ! NAMESPACE_ID_IS_EMPTY(NAMESPACE_ID)
namespace NAMESPACE_ID {
# endif
#elif ! NAMESPACE_ID_IS_EMPTY(NAMESPACE_ID)
}
#undef NAMESPACE_ID_IS_EMPTY
#undef NAMESPACE_ID_IS_EMPTY_
#endif
// same_ish<T,U> concept utility, removes cvref to match qualifed types
// (ToDo: move to a generic traits lib)
template <typename T, typename U>
concept same_ish = std::is_same_v<U, std::remove_cvref_t<T>>;
// rvalue<T> concept utility, false for lvalue T
// (ToDo: move to a generic traits lib)
template <typename T>
concept rvalue = std::is_rvalue_reference_v<T&&>;
// apply_ref and copy_ref utilities
//
namespace impl {
template<typename L, typename R>
auto apply_ref()
{
if constexpr (std::is_reference_v<L>)
{
if constexpr (std::is_lvalue_reference_v<L>)
return std::type_identity<R&>{};
else
return std::type_identity<R&&>{};
}
else
return std::type_identity<R>{};
}
template<typename L, typename R>
auto apply_cv()
{
if constexpr (std::is_const_v<L>)
{
if constexpr (std::is_volatile_v<L>)
return std::type_identity<R const volatile>{};
else
return std::type_identity<R const>{};
}
else
{
if constexpr (std::is_volatile_v<L>)
return std::type_identity<R volatile>{};
else
return std::type_identity<R>{};
}
}
} // impl
// apply_ref<L,R> applies any reference qualifier on L to R
// => reference collapse if R is already a reference
template <typename L, typename R>
using apply_ref = decltype(impl::apply_ref<L,R>());
template <typename L, typename R>
using apply_ref_t = typename apply_ref<L,R>::type;
// copy_ref<L,R> imposes any reference qualifier on L to R
//
template <typename L, typename R>
using copy_ref = apply_ref<L, std::remove_reference_t<R>>;
template <typename L, typename R>
using copy_ref_t = typename copy_ref<L,R>::type;
// apply_cv<L,R> applies any cv qualifier on L to R
//
template <typename L, typename R>
using apply_cv = decltype(impl::apply_cv<L,R>());
template <typename L, typename R>
using apply_cv_t = typename apply_cv<L,R>::type;
// copy_cv<L,R> imposes any cv qualifiers on L to R, removing any ref
//
template <typename L, typename R>
using copy_cv = apply_cv<L, std::remove_cvref_t<R>>;
template <typename L, typename R>
using copy_cv_t = typename copy_cv<L,R>::type;
template <typename L, typename R>
using apply_cvref = apply_ref<L,apply_cv_t<std::remove_reference_t<L>,R>>;
template <typename L, typename R>
using apply_cvref_t = typename apply_cvref<L,R>::type;
template <typename L, typename R>
using copy_cvref = apply_cvref<L,std::remove_cvref_t<R>>;
template <typename L, typename R>
using copy_cvref_t = typename copy_cvref<L,R>::type;
// c_array_t<T,I...> is an alias to array type T[I][...]
// e.g. c_array_t<int> -> int (not an array if T is not)
// c_array_t<int,0,2> -> int[0][2]
//
namespace impl {
template <typename T, int...>
extern T c_array_tv;
//
template <typename T, int... I>
using c_array_t = decltype(c_array_tv<T,I...>);
//
template <typename T, int J, int...I>
extern c_array_t<T,I...> c_array_tv<T, J, I...>[J];
}// impl
//
template <typename T, int... I>
using c_array_t = impl::c_array_t<T,I...>;
// is_array_v<A>
// same as std::is_array_v but supports zero-size array
// (detects array by 'decay' adjustment of a requires-expr parameter
// after is_object has rejected reference, void and function types)
//
template <typename A>
inline constexpr bool is_array_v = requires {
requires std::is_object_v<A>;
requires requires (A p) {requires ! std::is_same_v<A, decltype(p)>;};
};
// is_bounded_array_v<A>
// same as std::is_bounded_array_v but supports zero-size array
//
template <typename A>
inline constexpr bool is_bounded_array_v = is_array_v<A>
&& ! std::is_unbounded_array_v<A>;
// remove_extent_t<A>
namespace impl {
template <typename A> requires (! is_array_v<A>)
auto remove_extent_f() -> std::type_identity<A>;
//
template <typename A> requires is_array_v<A>
auto remove_extent_f(A a={}) -> std::remove_pointer<decltype(a)>;
}
// remove_extent_t<A>
// same as std::remove_extent_t but supports zero-size array
//
template <typename A> using remove_extent_t =
typename decltype(impl::remove_extent_f<A>())::type;
// remove_all_extents<A>
// same as std::remove_all_extents but supports zero-size array
//
template <typename A>
struct remove_all_extents { using type = A; };
//
template <typename A> requires is_array_v<A>
struct remove_all_extents<A> {
using type = typename remove_all_extents<remove_extent_t<A>>::type;
};
// remove_all_extents_t<A>
// same as std::remove_all_extents_t but supports zero-size array
//
template <typename A>
using remove_all_extents_t = typename remove_all_extents<A>::type;
// rank_v<A>:
// same as std::rank_v but supports zero-size array
//
template <typename A>
inline constexpr auto rank_v = [] {
if constexpr (is_array_v<A>)
return 1 + rank_v<remove_extent_t<A>>; // recurse
else return 0;
}();
// c_array concept: matches C array, including references to C array,
// including zero-size array, but not including unbounded array type.
// The second parameter allows to constrain or test the element type,
// cv unqualified: c_array<char> auto& a = "a"; matches const char
//
template <typename A,
typename E = remove_extent_t<std::remove_cvref_t<A>>>
concept c_array = is_bounded_array_v<std::remove_cvref_t<A>>
&& requires (std::remove_cvref_t<A> p)
{ requires std::is_same_v<E*,decltype(p)>; };
// flat_size<A>: total number of elements in array A, or 1 for non array
// computed by recursion; the product of extents of all ranks of A
// (and not by sizeof which may fail for zero-size arrays)
//
template <typename Ar>
inline constexpr auto flat_size = [] {
using A = std::remove_cvref_t<Ar>;
using E = remove_extent_t<A>;
if constexpr (is_array_v<E>)
return std::extent_v<A> * flat_size<E>; // recurse
else return std::extent_v<A> + !is_array_v<A>;
}();
// same_extents<A,B>: trait to tell if A and B have the same extents;
// array types with the same extents or both rank 0 (non-array) types
//
template <typename A, typename B>
inline constexpr bool same_extents =
(std::rank_v<A> == 0 && std::rank_v<B> == 0)
|| (std::extent_v<A> == std::extent_v<B>
&& same_extents<remove_extent_t<A>,remove_extent_t<B>>);
// c_array_unpadded concept:
// matches C array (and reference-to-array) that has no padding,
// i.e. sizeof array == total element count times sizeof element
// (false on MSVC for zero-size arrays, true on GCC and Clang)
// (are there other cases of 'padded' arrays? Best be paranoid)
//
template <typename A>
concept c_array_unpadded = c_array<A>
&& flat_size<A> * sizeof(remove_all_extents_t<
std::remove_cvref_t<A>>) == sizeof(A);
// extent_removed_t<T> remove_extent, under any reference qualifier
// e.g. extent_removed_t<int(&&)[1][2]> -> int(&&)[2]
template <c_array A>
using extent_removed_t = apply_ref_t<A,remove_extent_t<
std::remove_reference_t<A>>>;
// all_extents_removed_t<T> remove_all_extents, under any ref qualifier
// e.g. all_extents_removed_t<int(&)[1][2]> -> int&
template <typename T>
using all_extents_removed_t = apply_ref_t<T,remove_all_extents_t<
std::remove_reference_t<T>>>;
// flat_cast_t<A> type of the flattened array A, preserving cvref quals
// e.g. flat_cast_t<int const(&)[2][3]> -> int const(&)[6]
//
template <c_array_unpadded A, typename E = remove_all_extents_t<
std::remove_reference_t<A>>>
using flat_cast_t = apply_ref_t<A, E[flat_size<A>]>;
// flat_cast(a) cast to flat_cast_t, a reinterpret_cast for an ND array.
// Returns a, the identity, for 1D array so can be constexpr.
//
// Prefer flat_index(a,i) over flat_cast(a)[i] to avoid reinterpet_cast.
//
template <c_array_unpadded A>
constexpr auto&& flat_cast(A&& a) noexcept {
if constexpr (rank_v<std::remove_cvref_t<A>> == 1)
return (A&&)a;
else
return reinterpret_cast<flat_cast_t<A&&>>(a);
}
// subscript(a,i)
// returns a[i], an rvalue if argument 'a' is an array rvalue
// workaround for MSVC https://developercommunity.visualstudio.com/t/
// subscript-expression-with-an-rvalue-array-is-an-xv/1317259
//
template <c_array A, typename Int = int>
constexpr auto subscript(A&& a, Int i = 0) noexcept
-> extent_removed_t<A&&>
{
if constexpr (std::is_lvalue_reference_v<decltype(a)>)
return a[i];
else {
using R = extent_removed_t<A&&>;
return [](R&v)noexcept->R {return R(v);}(a[i]); // move(a[i]) equiv
}
}
// flat_index_recurse(a,i)
// Returns the element at index i of the flattened array. A recursive
// implementation is constexpr-correct but may give poor runtime codegen
// if it isn't inlined with the div/mod arithmetic folded out.
//
constexpr auto& flat_index_recurse(c_array auto& a, auto i)
noexcept
{
using E = decltype(a[0]);
if constexpr (c_array<E>)
{
constexpr auto M = flat_size<E>;
return flat_index_recurse(a[i/M], i%M);
}
else return a[i];
}
// flat_index(a,i)
// for non-array a returns a (the identity function, and i is ignored)
// else returns the element at index i of the flattened array.
// Non-constant evaluation avoids div/mod maths, which has poor codegen.
// Note that the index is not bounds-checked; constant evaluation fails
// for out-of-bounds access. Non-constant evaluated access should use
// static analysis and instrumented runs to check for bounds errors.
//
template <typename A, typename Int = int>
constexpr auto flat_index(A&& a, Int i = 0) noexcept
-> all_extents_removed_t<A&&>
{
using R = all_extents_removed_t<A&&>;
constexpr auto mover = [](R&v)noexcept->R{return R(v);};
if constexpr (!c_array<A>)
return mover(a);
else if (std::is_constant_evaluated() || ! c_array_unpadded<A>)
{
using E = std::remove_cvref_t<decltype(a[0])>;
if constexpr (rank_v<E> == 0) {
return mover(a[i]);
}
else if constexpr (rank_v<E> == 1) {
constexpr auto N = std::extent_v<E>;
return mover(a[i/N][i%N]);
}
else if constexpr (rank_v<E> == 2) {
constexpr auto M = std::extent_v<E,0>;
constexpr auto N = std::extent_v<E,1>;
return mover(a[i/N/M][i/N%M][i%N]);
}
else if constexpr (rank_v<E> == 3) {
constexpr auto L = std::extent_v<E,0>;
constexpr auto M = std::extent_v<E,1>;
constexpr auto N = std::extent_v<E,2>;
return mover(a[i/L/M/N][i/N/M%L][i/N%M][i%N]);
}
else
return mover(flat_index_recurse(a,i));
}
else
return mover(flat_cast(a)[i]); // No bounds check
}
// Comparison concepts from std lib extended to include array type
// three_way_comparable[_with]
// equality_comparable[_with]
// totally_ordered[_with]
//
template <typename A, class Cat = std::partial_ordering>
concept three_way_comparable =
std::three_way_comparable<all_extents_removed_t<A>, Cat>;
//
template <typename T> using is_three_way_comparable
= std::bool_constant< three_way_comparable<T>>;
template <typename L, typename R, class Cat = std::partial_ordering>
concept three_way_comparable_with =
std::three_way_comparable_with<all_extents_removed_t<L>,
all_extents_removed_t<R>, Cat>
&& same_extents<std::remove_cvref_t<L>,std::remove_cvref_t<R>>;
template <typename L, typename R>
using is_three_way_comparable_with = std::bool_constant<
three_way_comparable_with<L,R>>;
template <typename A>
concept equality_comparable =
std::equality_comparable<all_extents_removed_t<A>>;
//
template <typename T> using is_equality_comparable
= std::bool_constant< equality_comparable<T>>;
template <typename L, typename R>
concept equality_comparable_with =
std::equality_comparable_with<all_extents_removed_t<L>,
all_extents_removed_t<R>>
&& same_extents<std::remove_cvref_t<L>,std::remove_cvref_t<R>>;
template <typename L, typename R>
using is_equality_comparable_with = std::bool_constant<
equality_comparable_with<L,R>>;
template <typename A>
concept totally_ordered =
std::totally_ordered<all_extents_removed_t<A>>;
template <typename L, typename R>
concept totally_ordered_with =
std::totally_ordered_with<all_extents_removed_t<L>,
all_extents_removed_t<R>>
&& same_extents<std::remove_cvref_t<L>,std::remove_cvref_t<R>>;
template <typename L, typename R = L>
using compare_three_way_result_t =
std::compare_three_way_result_t<all_extents_removed_t<L>,
all_extents_removed_t<R>>;
namespace impl {
// pointer_equality_comparable_with
// A helper concept for lml::equal_to and lml::not_equal_to
// c.f. pointer equality requirements for std::ranges::equal_to
//
template <typename P, typename Q>
concept pointer_equality_comparable_with =
! c_array<P> && ! c_array<Q>
&& requires (P&& l, Q&& r) { {l == r} -> std::same_as<bool>; }
&& std::convertible_to<P, const volatile void*>
&& std::convertible_to<Q, const volatile void*>
&& (! requires(P&& l, Q&& r) { operator==(static_cast<P&&>(l),
static_cast<Q&&>(r)); }
&& ! requires(P&& l, Q&& r)
{ static_cast<P&&>(l).operator==(static_cast<Q&&>(r)); }
);
// pointer_less_than_comparable_with, as above for lml::less functor
//
template <typename P, typename Q>
concept pointer_less_than_comparable_with =
! c_array<P> && ! c_array<Q>
&& requires (P&& l, Q&& r) { {l < r} -> std::same_as<bool>; }
&& std::convertible_to<P, const volatile void*>
&& std::convertible_to<Q, const volatile void*>
&& (! requires(P&& l, Q&& r) { operator<(static_cast<P&&>(l),
static_cast<Q&&>(r)); }
&& ! requires(P&& l, Q&& r)
{ static_cast<P&&>(l).operator<(static_cast<Q&&>(r)); }
);
} // impl
// compare_three_way
// A version of std::compare_three_way extended to compare arrays
//
struct compare_three_way
{
template <typename L, typename R>
requires three_way_comparable_with<L,R>
constexpr auto operator()(L&& l, R&& r) const
noexcept(noexcept(std::compare_three_way{}(flat_index((L&&)l),
flat_index((R&&)r)) ))
-> compare_three_way_result_t<L,R>
{
if constexpr (std::three_way_comparable_with<L,R>)
return std::compare_three_way{}((L&&)l, (R&&)r);
else
{
for (int i = 0; i != flat_size<L>; ++i)
{
auto c = std::compare_three_way{}(flat_index((L&&)l,i),
flat_index((R&&)r,i));
if (c != 0)
return c;
}
return compare_three_way_result_t<L,R>::equivalent;
}
}
template <typename A>
constexpr auto operator()(A const& l, A const& r) const noexcept(
noexcept(operator()<A const&, A const&>(l,r)))
{
return operator()<A const&, A const&>(l,r);
}
using is_transparent = void;
};
// equal_to functor corrected to compare arrays, not array ids
//
struct equal_to
{
template <typename L, typename R>
requires (equality_comparable_with<L,R>
|| impl::pointer_equality_comparable_with<all_extents_removed_t<L>,
all_extents_removed_t<R>>)
constexpr bool operator()(L&& l, R&& r) const
noexcept(noexcept(flat_index((L&&)l) == flat_index((R&&)r)))
{
if constexpr (! c_array<L>)
return (L&&)l == (R&&)r;
else
{
for (int i = 0; i != flat_size<L>; ++i)
if ( flat_index((L&&)l,i) != flat_index((R&&)r,i) )
return false;
return true;
}
}
template <typename A>
constexpr bool operator()(A const& l, A const& r) const noexcept(
noexcept(operator()<A const&, A const&>(l,r)))
{
return operator()<A const&, A const&>(l,r);
}
using is_transparent = void;
};
// not_equal_to functor corrected to compare arrays, not array ids
//
struct not_equal_to
{
template <typename L, typename R>
requires (
equality_comparable_with<L,R> ||
impl::pointer_equality_comparable_with<all_extents_removed_t<L>,
all_extents_removed_t<R>>)
constexpr bool operator()(L&& l, R&& r) const
noexcept(noexcept(flat_index((L&&)l) == flat_index((R&&)r)))
{
return ! equal_to{}((L&&)l, (R&&)r);
}
template <typename A>
constexpr bool operator()(A const& l, A const& r) const noexcept(
noexcept(operator()<A const&, A const&>(l,r)))
{
return operator()<A const&, A const&>(l,r);
}
using is_transparent = void;
};
// less functor corrected to compare arrays, not array ids
//
struct less
{
template <typename L, typename R>
requires (
totally_ordered_with<L,R> ||
impl::pointer_less_than_comparable_with<all_extents_removed_t<L>,
all_extents_removed_t<R>>)
constexpr bool operator()(L&& l, R&& r) const
noexcept(noexcept(flat_index((L&&)l) < flat_index((R&&)r)))
{
if constexpr (impl::pointer_less_than_comparable_with<L,R>)
{
if (std::is_constant_evaluated())
return l < r;
else {
using void_cv = void const volatile;
return
reinterpret_cast<UINTPTR>(static_cast<void_cv*>((L&&)l))
< reinterpret_cast<UINTPTR>(static_cast<void_cv*>((R&&)r));
}
}
else if constexpr (! c_array<L>)
{
return (L&&)l < (R&&)r;
}
else
{
for (int i = 0; i != flat_size<L>; ++i)
if ( flat_index((L&&)l,i) != flat_index((R&&)r,i) )
return flat_index((L&&)l,i) < flat_index((R&&)r,i);
return false;
}
}
template <typename A>
constexpr bool operator()(A const& l, A const& r) const noexcept(
noexcept(operator()<A const&, A const&>(l,r)))
{
return operator()<A const&, A const&>(l,r);
}
using is_transparent = void;
};
#undef UINTPTR
namespace impl {
template <typename T>
struct member_default_3way_check
{
T v;
auto operator<=>(member_default_3way_check const&) const = default;
};
template <typename T>
struct member_default_equality_check
{
T v;
bool operator==(member_default_equality_check const&) const = default;
};
// detect gcc bug 93480
inline constexpr bool GCC10_C_ARRAY_COMPARE_WORKAROUND = []{
struct A { int v[1]; bool operator==(A const&) const = default; };
return A{} != A{}; // evaluates true with gcc bug #93480
}();
} // impl
// member_default_3way
// trait to check if member of type T can have defaulted <=>
//
template <typename T>
inline constexpr bool member_default_3way
= std::three_way_comparable<impl::member_default_3way_check<T>>
&& ! (is_array_v<T> && impl::GCC10_C_ARRAY_COMPARE_WORKAROUND);
template <typename T> using is_member_default_3way
= std::bool_constant<member_default_3way<T>>;
// member_default_equality
// trait to check if member of type T can have defaulted ==
//
template <typename T>
inline constexpr bool member_default_equality
= std::equality_comparable<impl::member_default_equality_check<T>>
&& ! (is_array_v<T> && impl::GCC10_C_ARRAY_COMPARE_WORKAROUND);
template <typename T> using is_member_default_equality
= std::bool_constant<member_default_equality<T>>;
// Detect language support for array copy semantics as proposed in P1997
//
inline constexpr bool is_copyable_array = std::copyable<int[1]>;
// is_X_constructible<T> array versions of std::is_X_assignable traits
//
template <typename T, typename...U>
inline constexpr bool is_constructible_v
= std::is_constructible_v<T,U...>;
template <c_array T, same_ish<T> U>
inline constexpr bool is_constructible_v<T,U>
= std::is_constructible_v<all_extents_removed_t<T>
,all_extents_removed_t<U>>;
template <typename T, typename U> using is_constructible
= std::bool_constant<is_constructible_v<T,U>>;
template <typename T, typename...U>
inline constexpr bool is_trivially_constructible_v
= std::is_trivially_constructible_v<T,U...>;
template <c_array T, same_ish<T> U>
inline constexpr bool is_trivially_constructible_v<T,U>
= std::is_trivially_constructible_v<all_extents_removed_t<T>
,all_extents_removed_t<U>>;
template <typename T, typename U> using is_trivially_constructible
= std::bool_constant<is_trivially_constructible_v<T,U>>;
template <typename T, typename...U>
inline constexpr bool is_nothrow_constructible_v
= std::is_nothrow_constructible_v<T,U...>;
template <c_array T, same_ish<T> U>
inline constexpr bool is_nothrow_constructible_v<T,U>
= std::is_nothrow_constructible_v<all_extents_removed_t<T>
,all_extents_removed_t<U>>;
template <typename T, typename U> using is_nothrow_constructible
= std::bool_constant<is_nothrow_constructible_v<T,U>>;
// assignable_from<L,R> array-enabled version of std::assignable_from
//
template <typename L, typename R>
concept assignable_from
= (is_copyable_array || ! c_array<L>
? std::assignable_from<L,R>
: std::assignable_from<all_extents_removed_t<L>,
all_extents_removed_t<R>>
&& same_extents<std::remove_cvref_t<L>,
std::remove_cvref_t<R>>);
// is_X_assignable<T,U> array versions of std::is_X_assignable traits
//
template <typename T, typename U>
inline constexpr bool is_assignable_v
= (is_copyable_array || ! c_array<T>
? std::is_assignable_v<T,U>
: std::is_assignable_v<all_extents_removed_t<T>,
all_extents_removed_t<U>>);
template <typename T, typename U> using is_assignable
= std::bool_constant<is_assignable_v<T,U>>;
template <typename T, typename U>
inline constexpr bool is_trivially_assignable_v
= (is_copyable_array || ! c_array<T>
? std::is_trivially_assignable_v<T,U>
: std::is_trivially_assignable_v<all_extents_removed_t<T>,
all_extents_removed_t<U>>);
template <typename T, typename U> using is_trivially_assignable
= std::bool_constant<is_trivially_assignable_v<T,U>>;
template <typename T, typename U>
inline constexpr bool is_nothrow_assignable_v
= (is_copyable_array || ! c_array<T>
? std::is_nothrow_assignable_v<T,U>
: std::is_nothrow_assignable_v<all_extents_removed_t<T>,
all_extents_removed_t<U>>);
template <typename T, typename U> using is_nothrow_assignable
= std::bool_constant<is_nothrow_assignable_v<T,U>>;
// is_X_assignable<T> array versions of std::is_X_assignable traits
//
template <typename T>
inline constexpr bool is_copy_assignable_v = (is_copyable_array
? std::is_copy_assignable_v<T>
: std::is_copy_assignable_v<all_extents_removed_t<T>>);
template <typename T> using is_copy_assignable
= std::bool_constant<is_copy_assignable_v<T>>;
template <typename T>
inline constexpr bool is_move_assignable_v = (is_copyable_array
? std::is_move_assignable_v<T>
: std::is_move_assignable_v<all_extents_removed_t<T>>);
template <typename T> using is_move_assignable
= std::bool_constant<is_move_assignable_v<T>>;
template <typename T>
inline constexpr bool is_trivially_copy_assignable_v = (is_copyable_array
? std::is_trivially_copy_assignable_v<T>
: std::is_trivially_copy_assignable_v<all_extents_removed_t<T>>);
template <typename T> using is_trivially_copy_assignable
= std::bool_constant<is_trivially_copy_assignable_v<T>>;
template <typename T>
inline constexpr bool is_trivially_move_assignable_v = (is_copyable_array
? std::is_trivially_move_assignable_v<T>
: std::is_trivially_move_assignable_v<all_extents_removed_t<T>>);
template <typename T> using is_trivially_move_assignable
= std::bool_constant<is_trivially_move_assignable_v<T>>;
template <typename T>
inline constexpr bool is_nothrow_copy_assignable_v = (is_copyable_array
? std::is_nothrow_copy_assignable_v<T>
: std::is_nothrow_copy_assignable_v<all_extents_removed_t<T>>);
template <typename T> using is_nothrow_copy_assignable
= std::bool_constant<is_nothrow_copy_assignable_v<T>>;
template <typename T>
inline constexpr bool is_nothrow_move_assignable_v = (is_copyable_array
? std::is_nothrow_move_assignable_v<T>
: std::is_nothrow_move_assignable_v<all_extents_removed_t<T>>);
template <typename T> using is_nothrow_move_assignable
= std::bool_constant<is_nothrow_move_assignable_v<T>>;
// empty_list_initializable<T> concept
// true if T can be copy initialized from empty list; T v = {};
// i.e. T has a default constructor, not explicit (or deleted?)
//
template <typename T>
concept empty_list_initializable = requires (void f(T&&)) { f({}); };
//
template <typename T>
using is_empty_list_initializable = std::bool_constant<
empty_list_initializable<T> >;
// empty_list_assignable<T> concept:
// true if elements can be assigned to from an empty init-list, v = {}
// (true for array of empty_list_assignable element type).
//
template <typename T>
concept empty_list_assignable = (is_copyable_array
? requires (T v) { static_cast<T>(v) = {}; }
: requires (all_extents_removed_t<T> v) {
static_cast<all_extents_removed_t<T>>(v) = {};
});
template <typename T>
using is_empty_list_assignable = std::bool_constant<
empty_list_assignable<T> >;
template <typename T>
using is_nothrow_empty_list_assignable = std::bool_constant<
noexcept(std::declval<all_extents_removed_t<T>&>() = {})>;
// assign_to customization point to specialize as a reference-wrapper
// with operator= overloads
// invoked by assign() function for types with assign_to specialization
//
template <typename L> struct assign_to;
template <typename L> assign_to(L&&) -> assign_to<L&&>;
template <typename L> concept assign_toable
= requires { sizeof(assign_to<L>); };
// assign_to<c_array> specialization for array assignment, if needed.
// operator=(R) -> L& returns the unwrapped type, not the wrapper type.
//
template <c_array L>
requires (! std::copyable<L>)
struct assign_to<L>
{
L& l;
using value_type = std::remove_reference_t<L>;
// operator=({}) overload for emtpy braced-init
//
constexpr L& operator=(std::true_type) const
noexcept(noexcept(flat_index(l) = {}))
requires empty_list_assignable<L&>
{
for (int i = 0; i != flat_size<L>; ++i)
flat_index(l, i) = {};
return l;
}
// operator=(lval) overload for array lvalues (and rvalue variables)
//
template <c_array R>
requires assignable_from<L, R&&>
constexpr L& operator=(R&& r) const
noexcept(noexcept(flat_index(l) = flat_index((R&&)r)))
{
for (int i = 0; i != flat_size<L>; ++i)
flat_index(l, i) = flat_index((R&&)r, i);
return l;
}
// operator=(rval) overload for array rvalue from braced-init
//
constexpr L& operator=(value_type const& r) const
noexcept(noexcept(flat_index(l) = flat_index(r)))
{
for (int i = 0; i != flat_size<L>; ++i)
flat_index(l, i) = flat_index(r, i);
return l;
}
};
// assign(l) returns assign_to{l}, if assign_toable, else reference-to-l
template <typename L>
constexpr decltype(auto) assign(L&& l) noexcept
{
if constexpr (assign_toable<L&&>)
return std::add_const_t<assign_to<L&&>>{l};
else
return (L&&)l;
}
template <c_array L, typename...T>
requires (assignable_from<extent_removed_t<L>,T> && ...)
constexpr auto& assign_elements(L&& t, T&&...v)
noexcept(noexcept( ((assign(extent_removed_t<L>(*t)) = (T&&)v),...) ))
{
static_assert(sizeof...(T) == std::extent_v<std::remove_cvref_t<L>>,
"assign_elements requires all elements to be assigned.");
auto p = t;
((assign(static_cast<extent_removed_t<L>>(*p++)) = (T&&)v),...);
return t;
}
using size_t = decltype(sizeof"");
// tupl_t<Tupl> alias of the member typedef tupl_t
//
template <typename Tupl>
using tupl_t = typename std::remove_cvref_t<Tupl>::tupl_t;
// tupl_like_t<Tupl> tupl_t with same cvref qualifiers as Tupl
//
template <typename Tupl>
using tupl_like_t = copy_cvref_t<Tupl,tupl_t<Tupl>>;
// tuplish concept, requires complete type T, possibly cvref qualified,
// with a member alias 'tupl_t' to which T is implicitly convertible
// (e.g. by deriving from tupl) and a map function such that
// map(tupl,functor) returns functor(x...) called on tupl members x...
//
// Here, implicit conversion T -> tupl_t is tested by reference binding
// and a void functor is used to test for presence of a map function
//
constexpr auto tuplish_void_fn = [](auto&&...){};
template <typename T>
concept tuplish = requires (T& v) {
{map(tupl_like_t<T&>(v), tuplish_void_fn)} -> std::same_as<void>;
};
// as_tupl_t(tup) cast to tupl_like_t (fwd the arg for value category)
//
template <tuplish T>
constexpr auto as_tupl_t(T&& t) noexcept -> tupl_like_t<T&&>
{ return static_cast<tupl_like_t<T&&>>(t); }
// typelist
//
template <typename TL>
inline constexpr bool is_typelist_v = false;
template <template <typename...> class L, typename...T>
inline constexpr bool is_typelist_v<L<T...>> = true;
template <typename TL>
concept typelist = is_typelist_v<TL>;
// type_list
//
template <typename...> struct type_list {};
// type_list_size_v<T> the number of elements E in type list T = L<E...>
//
template <typelist L> inline constexpr size_t type_list_size_v
= NOT_DEFINED(type_list_size_v<L>);
//
template <template <typename...> class L, typename...E>
inline constexpr auto type_list_size_v<L<E...>> = sizeof...(E);
// tupl_size_v<Tupl> number of tupl elements (requires complete type)
//
template <tuplish Tupl> inline constexpr auto tupl_size_v
= type_list_size_v<tupl_t<Tupl>>;
/* types_all: types from a type list, or pairs of types from two lists,
all satisfy predicate P
types_all<P,LT> -> (P<T>() && ...) for LT = L<T...>
types_all<P,LT,LU> -> (P<T,U>() && ...) for LT = L<L...> LU = L<U...>
*/
template <template <typename...> class P, typelist L, typelist... R>
requires ((type_list_size_v<L> == type_list_size_v<R>) && ...)
inline constexpr bool types_all
= NOT_DEFINED(types_all<P,L,R...>);
//
template <template <typename...> class L, typename...T,
template <typename...> class P>
inline constexpr bool types_all<P,L<T...>> = (P<T>() && ...);
//
template <template <typename...> class L, typename...T,
template <typename...> class R, typename...U,
template <typename...> class P>
requires (sizeof...(T) == sizeof...(U))
inline constexpr bool types_all<P,L<T...>,R<U...>> = (P<T,U>() && ...);
//static_assert(types_all<std::is_fundamental, int>);
template <typename...E>
concept move_assignable = (is_move_assignable_v<E> && ...);
template <typename...E>
concept copy_assignable = (is_copy_assignable_v<E> && ...);
template <typename L, typename R>
using is_lval_assignable = is_assignable<L&,R>;
template <typename L, typename R>
using is_lval_nothrow_assignable = is_nothrow_assignable<L&,R>;
// non_narrow_assignable<T> 'uniform' aggregate assignment property
//
template <typename L, typename R = L>
concept non_narrow_assignable
= requires (all_extents_removed_t<L> l
,all_extents_removed_t<R> r) { l={r}; };
//
template <typename L, typename R>
using is_non_narrow_assignable = std::bool_constant<
non_narrow_assignable<L,R> >;
// const_assignable<T> captures the property of references and proxies
// that they can be assigned to even if const qualified
//
template <typename L, typename R = L>
concept const_assignable
= requires (all_extents_removed_t<L> const lc
,all_extents_removed_t<R> r) { lc=r; };
//
template <typename L, typename R = L>
using is_const_assignable = std::bool_constant<
const_assignable<L,R> >;
// non_narrow_const_assignable<T> references and proxies can be assigned
// to even if const qualified - this also enforces non-narrowing
//
template <typename L, typename R>
concept non_narrow_const_assignable
= requires (all_extents_removed_t<L> const lc
,all_extents_removed_t<R> r) { lc={r}; };
//
template <typename L, typename R = L>
using is_non_narrow_const_assignable = std::bool_constant<
non_narrow_const_assignable<L,R> >;
// is_value<T>: T is an object type excluding const_assignable types
//
template <typename T>
using is_value = std::bool_constant<std::is_object_v<T>
&& ! const_assignable<T>>;
// tupl_val<T> concept: tuplish type with all elements object-type
// (note: is_object matches unbounded array T[], allows for FAM)
//
template <typename Tupl>
concept tupl_val
= tuplish<Tupl>
&& types_all<is_value, tupl_t<Tupl>>;
// tupl_tie<T> concept: matches tupl of reference-like elements detected
// as const-assignable; of assignable reference or proxy-reference type.
// (note: const_assignable matches unbounded array reference T(&)[])
//
template <typename Tupl>
concept tupl_tie
= tuplish<Tupl>
&& tupl_size_v<Tupl> != 0
&& types_all<is_const_assignable, tupl_t<Tupl>>;
// type_map<M,LT> -> L<M<T>...> for type list LT = L<T...>
//
template <template <typename...> class M, typename LT>
struct type_map;
//
template <template <typename...> class M,
template <typename...> class L, typename...T>
struct type_map<M, L<T...>> {
using type = L< M<T>... >;
};
//
template <template <typename...> class M, typename LT>
using type_map_t = typename type_map<M,LT>::type;
// tupl_fwd_t<t> apply the top-level cvref of tuplish t to its elements
// -> tupl<apply_cvref_t<t,E>...> where E... are element types of t
//
template <tuplish t>
struct tupl_fwd {
template <typename T> using apply_cvref = apply_cvref_t<t,T>;
using type = type_map_t<apply_cvref, tupl_t<t>>;
};
//
template <tuplish t>
using tupl_fwd_t = typename tupl_fwd<t>::type;
// type_pack_indexof<X,E...> Index of element of type X in pack E...
// Fails with a static_assert if there's no X or multiple Xs in E...
//
template <typename X, typename...E>
inline constexpr int type_pack_indexof = [] {
static_assert(sizeof...(E) > 0, "type_pack_indexof zero-size pack");
static_assert((std::same_as<X,E> + ...) == 1,
"type_pack_indexof error: pack must have a single matching type");
int k=0, r=-1;
return ((std::same_as<X,E> && r<0 ? r=k : ++k),...), r;
}();
// type_list_indexof<X,TL> Index of element of type X in type list TL
//
template <typename X, typename TL> extern const int type_list_indexof;
//
template <typename X, template <typename...> class L, typename...E>
inline constexpr int type_list_indexof<X,L<E...>>
= type_pack_indexof<X,E...>;
#ifndef TYPEPACKEL // defined as __type_pack_element<I,E..> if builtin
//
// Implement type_pack_element_t
//
namespace impl {
//
template <
typename X0=void,typename X1=void,typename X2=void,typename X3=void,
typename X4=void,typename X5=void,typename X6=void,typename X7=void,
typename X8=void,typename X9=void,typename Xa=void,typename Xb=void,
typename Xc=void,typename Xd=void,typename Xe=void,typename Xf=void,
typename...T>
constexpr auto type_pack_element_t = []<size_t I>()
{
switch (I)
{
case 0x0: if constexpr (I==0x0) return std::type_identity<X0>{};
case 0x1: if constexpr (I==0x1) return std::type_identity<X1>{};
case 0x2: if constexpr (I==0x2) return std::type_identity<X2>{};
case 0x3: if constexpr (I==0x3) return std::type_identity<X3>{};
case 0x4: if constexpr (I==0x4) return std::type_identity<X4>{};
case 0x5: if constexpr (I==0x5) return std::type_identity<X5>{};
case 0x6: if constexpr (I==0x6) return std::type_identity<X6>{};
case 0x7: if constexpr (I==0x7) return std::type_identity<X7>{};
case 0x8: if constexpr (I==0x8) return std::type_identity<X8>{};
case 0x9: if constexpr (I==0x9) return std::type_identity<X9>{};
case 0xa: if constexpr (I==0xa) return std::type_identity<Xa>{};
case 0xb: if constexpr (I==0xb) return std::type_identity<Xb>{};
case 0xc: if constexpr (I==0xc) return std::type_identity<Xc>{};
case 0xd: if constexpr (I==0xd) return std::type_identity<Xd>{};
case 0xe: if constexpr (I==0xe) return std::type_identity<Xe>{};
case 0xf: if constexpr (I==0xf) return std::type_identity<Xf>{};
default: if constexpr (I>=0x10) return type_pack_element_t<T...>.
template operator()<I-0x10>();
}
};
#define TYPEPACKEL typename decltype(impl::type_pack_element_t<E...>\
.template operator()<I>())::type; // I,E... are 'baked in'
} // impl
#endif
// type_pack_element_t<I,T...> alias of element I in type pack T...
//
template <size_t I, typename...E> requires (I < sizeof...(E))
using type_pack_element_t = TYPEPACKEL; // TYPEPACKEL 'bakes in' I,E...
#undef TYPEPACKEL
// type_list_element<I,TL> type_identity of element I in type list TL
//
template <size_t I, typename TL>
extern std::type_identity<void> type_list_element;
//
template <size_t I, template <typename...> class L, typename...E>
std::type_identity<type_pack_element_t<I,E...>>
type_list_element<I,L<E...>>{};
// type_list_element_t<I,TL> type of element I in type list TL = L<E...>
//
template <size_t I, typename TL>
using type_list_element_t = typename decltype(
type_list_element<I,TL>)::type;
namespace impl {
// Helpers to compute tupl_move_t, recursively
//
template <typename U> auto tmove(...) -> std::remove_cvref_t<U>&&;
#ifdef _MSC_VER
template <typename U> auto tmove(...) -> std::remove_cvref_t<U>&
requires std::is_function_v<std::remove_cvref_t<U>>;
#endif
template <typename U> auto tmove(...) -> std::remove_cvref_t<U>
requires (sizeof(U)<=16 && !c_array<U> && std::is_trivially_copyable_v
<std::remove_cvref_t<U>>);
template<typename U> struct tmovet;
template<typename U> using tmove_t = typename tmovet<U>::type;
template<tuplish U> auto tmove(int) -> type_map_t<tmove_t,tupl_t<U>>;
template<typename U> void tmove(int) requires(!requires{tmove<U&>(0);});
template<typename U> struct tmovet {using type=decltype(tmove<U>(0));};
// Helpers to compute tupl_view_t, recursively
//
template <typename U> auto tview(...) -> std::remove_cvref_t<U> const&;
template <typename U> auto tview(...) -> std::remove_cvref_t<U>
requires (sizeof(U)<=16 && !c_array<U> && std::is_trivially_copyable_v
<std::remove_cvref_t<U>>);
template<typename U> struct tviewt;
template<typename U> using tview_t = typename tviewt<U>::type;
template<tuplish U> auto tview(int) -> type_map_t<tview_t,tupl_t<U>>;
template<typename U> void tview(int) requires(!requires{tview<U&>(0);});
template<typename U> struct tviewt {using type=decltype(tview<U>(0));};
} // impl
/*
tupl_move_t<U> -> U&&
tupl_view_t<U> -> U const&
or -> U for small non-array trivially-copyable types
or -> void for non-referencable types
or if U is a tupl, recursively transform its element types
*/
template <typename U> using tupl_move_t = impl::tmove_t<U>;
template <typename U> using tupl_view_t = impl::tview_t<U>;
//
inline constexpr size_t tupl_max_arity = 0x10;
/*
tupl<E...> primary template declaration; an aggregate struct
with [[no_unique_address]] attribute on all element member decls E...
*/
template <typename...E> struct tupl; // aggregate [[no_unique_address]]
/*
tupl CTAD deduces elements by-value, including arrays (and functions)
*/
template <typename...E> tupl(E const&...) -> tupl<E...>;
//
template<typename T> inline constexpr bool is_tupl_v = false;
template<typename...E>inline constexpr bool is_tupl_v<tupl<E...>>{true};
//
template<typename T> inline constexpr bool is_lupl_v = false;
/*
lupl<E...> 'layout compatible' tupl without [[no_unique_address]]
*/
template <typename...E> struct lupl; // aggregate, layout-compatibility
template <typename...E> lupl(E const&...) -> lupl<E...>;
template<typename...E>inline constexpr bool is_lupl_v<lupl<E...>>{true};
//
template<typename T>
concept tupl_or_lupl = is_tupl_v<tupl_t<T>> || is_lupl_v<tupl_t<T>>;
/*
Derived tuplish types with custom CTAD only, nothing else added
*/
template <typename...E> struct fwds : tupl<E...> {}; // forwarding refs
template <typename...E> struct lvals : tupl<E...> {}; // lvalue refs
template <typename...E> struct rvals : tupl<E...> {}; // rvalue refs
template <typename...E> struct cvals : tupl<E...> {}; // const view vals
/*
CTAD guides
*/
template <typename...E> fwds(E&&...) -> fwds<E&&...>;
template <typename...E> lvals(E&...) -> lvals<E&...>;
template <rvalue... RV> rvals(RV&&...) -> rvals<RV&&...>;
template <typename...E> cvals(E const&...) -> cvals<tupl_view_t<E>...>;
//
//
// tupl<T...> specializations VREPEATed for each arity
//
template <>
struct tupl<>
{
using tupl_t = tupl;
//
//
friend auto operator<=>(tupl const&,tupl const&)
= default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map([[maybe_unused]]T&& t, F f)noexcept(
noexcept(f()))
{ return f(); }
};
//
template <class X0>
struct tupl<X0>
{
using tupl_t = tupl;
//
NUA X0 x0;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0)))
{ return f(U(t).x0); }
};
//
template <class X0,class X1>
struct tupl<X0,X1>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1)))
{ return f(U(t).x0,U(t).x1); }
};
//
template <class X0,class X1,class X2>
struct tupl<X0,X1,X2>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;NUA X2 x2;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2)))
{ return f(U(t).x0,U(t).x1,U(t).x2); }
};
//
template <class X0,class X1,class X2,class X3>
struct tupl<X0,X1,X2,X3>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;NUA X2 x2;NUA X3 x3;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3); }
};
//
template <class X0,class X1,class X2,class X3,class X4>
struct tupl<X0,X1,X2,X3,X4>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;NUA X2 x2;NUA X3 x3;NUA X4 x4;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5>
struct tupl<X0,X1,X2,X3,X4,X5>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;NUA X2 x2;NUA X3 x3;NUA X4 x4;NUA X5 x5;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6>
struct tupl<X0,X1,X2,X3,X4,X5,X6>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;NUA X2 x2;NUA X3 x3;NUA X4 x4;NUA X5 x5;NUA X6 x6;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7>
struct tupl<X0,X1,X2,X3,X4,X5,X6,X7>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;NUA X2 x2;NUA X3 x3;NUA X4 x4;NUA X5 x5;NUA X6 x6;NUA X7 x7;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8>
struct tupl<X0,X1,X2,X3,X4,X5,X6,X7,X8>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;NUA X2 x2;NUA X3 x3;NUA X4 x4;NUA X5 x5;NUA X6 x6;NUA X7 x7;NUA X8 x8;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8,class X9>
struct tupl<X0,X1,X2,X3,X4,X5,X6,X7,X8,X9>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;NUA X2 x2;NUA X3 x3;NUA X4 x4;NUA X5 x5;NUA X6 x6;NUA X7 x7;NUA X8 x8;NUA X9 x9;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8,class X9,class Xa>
struct tupl<X0,X1,X2,X3,X4,X5,X6,X7,X8,X9,Xa>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;NUA X2 x2;NUA X3 x3;NUA X4 x4;NUA X5 x5;NUA X6 x6;NUA X7 x7;NUA X8 x8;NUA X9 x9;NUA Xa xa;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8,class X9,class Xa,class Xb>
struct tupl<X0,X1,X2,X3,X4,X5,X6,X7,X8,X9,Xa,Xb>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;NUA X2 x2;NUA X3 x3;NUA X4 x4;NUA X5 x5;NUA X6 x6;NUA X7 x7;NUA X8 x8;NUA X9 x9;NUA Xa xa;NUA Xb xb;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8,class X9,class Xa,class Xb,class Xc>
struct tupl<X0,X1,X2,X3,X4,X5,X6,X7,X8,X9,Xa,Xb,Xc>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;NUA X2 x2;NUA X3 x3;NUA X4 x4;NUA X5 x5;NUA X6 x6;NUA X7 x7;NUA X8 x8;NUA X9 x9;NUA Xa xa;NUA Xb xb;NUA Xc xc;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8,class X9,class Xa,class Xb,class Xc,class Xd>
struct tupl<X0,X1,X2,X3,X4,X5,X6,X7,X8,X9,Xa,Xb,Xc,Xd>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;NUA X2 x2;NUA X3 x3;NUA X4 x4;NUA X5 x5;NUA X6 x6;NUA X7 x7;NUA X8 x8;NUA X9 x9;NUA Xa xa;NUA Xb xb;NUA Xc xc;NUA Xd xd;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc,U(t).xd)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc,U(t).xd); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8,class X9,class Xa,class Xb,class Xc,class Xd,class Xe>
struct tupl<X0,X1,X2,X3,X4,X5,X6,X7,X8,X9,Xa,Xb,Xc,Xd,Xe>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;NUA X2 x2;NUA X3 x3;NUA X4 x4;NUA X5 x5;NUA X6 x6;NUA X7 x7;NUA X8 x8;NUA X9 x9;NUA Xa xa;NUA Xb xb;NUA Xc xc;NUA Xd xd;NUA Xe xe;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc,U(t).xd,U(t).xe)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc,U(t).xd,U(t).xe); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8,class X9,class Xa,class Xb,class Xc,class Xd,class Xe,class Xf>
struct tupl<X0,X1,X2,X3,X4,X5,X6,X7,X8,X9,Xa,Xb,Xc,Xd,Xe,Xf>
{
using tupl_t = tupl;
//
NUA X0 x0;NUA X1 x1;NUA X2 x2;NUA X3 x3;NUA X4 x4;NUA X5 x5;NUA X6 x6;NUA X7 x7;NUA X8 x8;NUA X9 x9;NUA Xa xa;NUA Xb xb;NUA Xc xc;NUA Xd xd;NUA Xe xe;NUA Xf xf;
//
friend auto operator<=>(tupl const&,tupl const&)
requires types_all<is_member_default_3way,tupl> = default;
//
template <same_ish<tupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc,U(t).xd,U(t).xe,U(t).xf)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc,U(t).xd,U(t).xe,U(t).xf); }
};
//
// lupl<T...> specializations VREPEATed for each arity
//
template <>
struct lupl<>
{
using tupl_t = lupl;
//
//
friend auto operator<=>(lupl const&,lupl const&)
= default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map([[maybe_unused]]T&& t, F f)noexcept(
noexcept(f()))
{ return f(); }
};
//
template <class X0>
struct lupl<X0>
{
using tupl_t = lupl;
//
X0 x0;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0)))
{ return f(U(t).x0); }
};
//
template <class X0,class X1>
struct lupl<X0,X1>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1)))
{ return f(U(t).x0,U(t).x1); }
};
//
template <class X0,class X1,class X2>
struct lupl<X0,X1,X2>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;X2 x2;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2)))
{ return f(U(t).x0,U(t).x1,U(t).x2); }
};
//
template <class X0,class X1,class X2,class X3>
struct lupl<X0,X1,X2,X3>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;X2 x2;X3 x3;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3); }
};
//
template <class X0,class X1,class X2,class X3,class X4>
struct lupl<X0,X1,X2,X3,X4>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;X2 x2;X3 x3;X4 x4;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5>
struct lupl<X0,X1,X2,X3,X4,X5>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;X2 x2;X3 x3;X4 x4;X5 x5;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6>
struct lupl<X0,X1,X2,X3,X4,X5,X6>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;X2 x2;X3 x3;X4 x4;X5 x5;X6 x6;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7>
struct lupl<X0,X1,X2,X3,X4,X5,X6,X7>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;X2 x2;X3 x3;X4 x4;X5 x5;X6 x6;X7 x7;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8>
struct lupl<X0,X1,X2,X3,X4,X5,X6,X7,X8>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;X2 x2;X3 x3;X4 x4;X5 x5;X6 x6;X7 x7;X8 x8;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8,class X9>
struct lupl<X0,X1,X2,X3,X4,X5,X6,X7,X8,X9>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;X2 x2;X3 x3;X4 x4;X5 x5;X6 x6;X7 x7;X8 x8;X9 x9;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8,class X9,class Xa>
struct lupl<X0,X1,X2,X3,X4,X5,X6,X7,X8,X9,Xa>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;X2 x2;X3 x3;X4 x4;X5 x5;X6 x6;X7 x7;X8 x8;X9 x9;Xa xa;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8,class X9,class Xa,class Xb>
struct lupl<X0,X1,X2,X3,X4,X5,X6,X7,X8,X9,Xa,Xb>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;X2 x2;X3 x3;X4 x4;X5 x5;X6 x6;X7 x7;X8 x8;X9 x9;Xa xa;Xb xb;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8,class X9,class Xa,class Xb,class Xc>
struct lupl<X0,X1,X2,X3,X4,X5,X6,X7,X8,X9,Xa,Xb,Xc>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;X2 x2;X3 x3;X4 x4;X5 x5;X6 x6;X7 x7;X8 x8;X9 x9;Xa xa;Xb xb;Xc xc;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8,class X9,class Xa,class Xb,class Xc,class Xd>
struct lupl<X0,X1,X2,X3,X4,X5,X6,X7,X8,X9,Xa,Xb,Xc,Xd>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;X2 x2;X3 x3;X4 x4;X5 x5;X6 x6;X7 x7;X8 x8;X9 x9;Xa xa;Xb xb;Xc xc;Xd xd;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc,U(t).xd)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc,U(t).xd); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8,class X9,class Xa,class Xb,class Xc,class Xd,class Xe>
struct lupl<X0,X1,X2,X3,X4,X5,X6,X7,X8,X9,Xa,Xb,Xc,Xd,Xe>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;X2 x2;X3 x3;X4 x4;X5 x5;X6 x6;X7 x7;X8 x8;X9 x9;Xa xa;Xb xb;Xc xc;Xd xd;Xe xe;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc,U(t).xd,U(t).xe)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc,U(t).xd,U(t).xe); }
};
//
template <class X0,class X1,class X2,class X3,class X4,class X5,class X6,class X7,class X8,class X9,class Xa,class Xb,class Xc,class Xd,class Xe,class Xf>
struct lupl<X0,X1,X2,X3,X4,X5,X6,X7,X8,X9,Xa,Xb,Xc,Xd,Xe,Xf>
{
using tupl_t = lupl;
//
X0 x0;X1 x1;X2 x2;X3 x3;X4 x4;X5 x5;X6 x6;X7 x7;X8 x8;X9 x9;Xa xa;Xb xb;Xc xc;Xd xd;Xe xe;Xf xf;
//
friend auto operator<=>(lupl const&,lupl const&)
requires types_all<is_member_default_3way,lupl> = default;
//
template <same_ish<lupl> T, typename F, typename U = T&&>
friend constexpr decltype(auto) map(T&& t, F f)noexcept(
noexcept(f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc,U(t).xd,U(t).xe,U(t).xf)))
{ return f(U(t).x0,U(t).x1,U(t).x2,U(t).x3,U(t).x4,U(t).x5,U(t).x6,U(t).x7,U(t).x8,U(t).x9,U(t).xa,U(t).xb,U(t).xc,U(t).xd,U(t).xe,U(t).xf); }
};
/*
get<I>(t)
*/
template <size_t I, tuplish T>
constexpr auto get(T&& t) noexcept
-> apply_cvref_t<T&&,type_list_element_t<I,tupl_t<T>>>
requires (I < tupl_size_v<T>)
{
using tuplt = tupl_t<T>;
using ret_t = apply_cvref_t<T&&,type_list_element_t<I,tuplt>>;
using ptr_t = std::remove_reference_t<ret_t>*;
struct E{constexpr E(...){}};
struct At{ E a[I+1]; ptr_t e; E b[tupl_size_v<tuplt>-I]; };
return map(static_cast<tupl_like_t<T>&>(t), [](auto&...a) noexcept
-> ret_t
{
NO_WARN_MISSING_BRACES(
return static_cast<ret_t>(*At{0,&a...,0}.e);
)
});
}
/*
get<T>(t)
*/
template <typename X>
constexpr auto&& get(tuplish auto&& t) noexcept
{
return get<type_list_indexof<X, tupl_t<decltype(t)>>>((decltype(t))t);
}
/*
tupl_mptr<I,T> member pointer getter
*/
template <size_t I, typename T> // defined for tupl or lupl derived
auto tupl_mptr = NOT_DEFINED(tupl_mptr<I,T>);
template <size_t I, typename T>
requires tupl_or_lupl<tupl_t<T>>
constexpr auto tupl_mptr<I,T>
= [] {
static_assert(I < tupl_size_v<T>, "tupl_mptr index out of bounds");
if constexpr(I==0x00)return&T::x0;else if constexpr(I==0x01)return&T::x1;else if constexpr(I==0x02)return&T::x2;else if constexpr(I==0x03)return&T::x3;else if constexpr(I==0x04)return&T::x4;else if constexpr(I==0x05)return&T::x5;else if constexpr(I==0x06)return&T::x6;else if constexpr(I==0x07)return&T::x7;else if constexpr(I==0x08)return&T::x8;else if constexpr(I==0x09)return&T::x9;else if constexpr(I==0x0a)return&T::xa;else if constexpr(I==0x0b)return&T::xb;else if constexpr(I==0x0c)return&T::xc;else if constexpr(I==0x0d)return&T::xd;else if constexpr(I==0x0e)return&T::xe;else if constexpr(I==0x0f)return&T::xf;
UNREACHABLE()
}();
/*
tupl_mptrs<T> return a tupl of member pointers
*/
template <typename T, size_t...I>
requires tupl_or_lupl<tupl_t<T>>
constexpr auto tupl_mptrs = tupl_mptrs<T,I...,sizeof...(I)>;
//
template <typename T, size_t...I>
requires (tupl_or_lupl<tupl_t<T>> && tupl_size_v<T> == sizeof...(I))
constexpr auto tupl_mptrs<T,I...> = tupl{tupl_mptr<I,T>...};
/*
assign_elements(t,v...) copy-or-move assigns all elements of a tupl
*/ // admits non-narrowing conversions
template <tuplish L, typename...T>
constexpr auto&& assign_elements(L&& l, T&&...v)
noexcept(types_all<is_lval_nothrow_assignable,tupl_t<L>,tupl<T...>>)
requires types_all<is_lval_assignable,tupl_t<L>,tupl<T...>>
{
map(as_tupl_t(l),
[&](auto&...a) noexcept(
types_all<is_lval_nothrow_assignable,tupl_t<L>,tupl<T...>>)
{ ((assign(a) = (T&&)v),...); });
return (L&&)l;
}
/*
swap(ta,tb) tupl swap specialization, includes const-assignable,
(std::swap already handles possibly-nested C array)
*/
template <tuplish L>
constexpr void swap(L& l, L& r)
noexcept(types_all<std::is_nothrow_swappable, tupl_t<L>>)
requires types_all<std::is_swappable, tupl_t<L>>
{
enum:bool {X = types_all<std::is_nothrow_swappable, tupl_t<L>>};
map(as_tupl_t(l), [&r](auto&...t) noexcept(X) {
map(as_tupl_t(r), [&t...](auto&...u) noexcept(X)
{
(std::ranges::swap(t,u),...);
});
});
}
/*
assign_to<tupl> customization for tuple-tuple assignment
*/
template <tuplish Lr>
struct assign_to<Lr>
{
using L = tupl_t<Lr>;
using value_type = std::conditional_t<tupl_tie<L>, const L, L>;
value_type& l;
template <tuplish R>
constexpr value_type& assign_from(R&& r) const
noexcept(types_all<is_lval_nothrow_assignable,L,tupl_t<R>>)
requires types_all<is_lval_assignable,L,tupl_t<R>>
{
enum:bool{ X = types_all<is_lval_nothrow_assignable,L,tupl_t<R>> };
if constexpr (tupl_tie<L>)
// copy or move assign according to RHS element reference types
[&]<template<typename...>class W
,typename...U>(W<U...>const& r) noexcept(X)
{
map(r, [&](auto&...u) noexcept(X) {
assign_elements(l, (U)u...);
});
}(static_cast<tupl_t<R>const&>(r));
else
// copy or move assign all elements decided by RHS reference type
map(as_tupl_t((R&&)r), [&](auto&&...u) noexcept(X)
{
assign_elements(l, (decltype(u))(u)...);
});
return l;
}
constexpr value_type& operator=(L& r) const
noexcept(std::is_nothrow_copy_assignable_v<L>)
requires (tupl_val<L> && std::is_copy_assignable_v<L>)
{
return l = r;
}
constexpr value_type& operator=(L&& r) const
noexcept(std::is_nothrow_move_assignable_v<L>)
requires (tupl_val<L> && std::is_move_assignable_v<L>)
{
return l = (L&&)r;
}
// assign from other tuplish
template <tuplish R>
constexpr value_type& operator=(R&& r) const
noexcept(types_all<is_lval_nothrow_assignable,L,tupl_t<R>>)
requires types_all<is_lval_assignable,L,tupl_t<R>>
{
return assign_from((R&&)r);
}
// move assign from braced init-list of rvalues; better match
constexpr value_type& operator=(tupl_move_t<L>&& r) const
noexcept(types_all<is_nothrow_move_assignable,L>)
requires types_all<is_move_assignable,L>
{
map(r, [&](auto&...u)
noexcept(types_all<is_nothrow_move_assignable,L>)
{
assign_elements(l, (tupl_move_t<decltype(u)>)(u)...);
});
return l;
}
// copy assign from braced init-list of lvalues; worse match
template <typename...>
constexpr value_type& operator=(tupl_view_t<L> const& r) const
noexcept(types_all<is_nothrow_copy_assignable,L>)
requires (types_all<is_copy_assignable,L>)
{
return assign_from(r);
}
// assign from empty braces
constexpr value_type& operator=(std::true_type) const
noexcept(types_all<is_nothrow_empty_list_assignable,L>)
requires types_all<is_empty_list_assignable,L>
{
map(l, [](auto&...t)
noexcept(types_all<is_nothrow_empty_list_assignable,L>)
{((assign(t)={}),...);});
return l;
}
};
/*
compare3way(l,r): Lexicographic 3-way comparison of tuplish types
compares any sets of 3-way comparable element types,
the smaller size tupl comparing less all else equal.
*/
template <int...I, tuplish L, tuplish R>
constexpr auto compare3way(L const& l,R const& r) noexcept
requires (tupl_size_v<L> != tupl_size_v<R>
|| types_all<is_three_way_comparable_with,tupl_t<L>,tupl_t<R>>)
{
enum { endl = tupl_size_v<L>
, endr = tupl_size_v<R> };
constexpr auto lr = endl<=>endr;
constexpr auto end = lr < 0 ? endl : endr;
constexpr compare_three_way cmp;
if constexpr (constexpr int J{I...}; J == end) return lr;
else if constexpr (sizeof...(I) == 0) return compare3way<0>(l,r);
else {
auto c = cmp(get<J>(l), get<J>(r));
return c != 0 ? c : compare3way<J+1>(l,r);
}
}
// compare3way(l,r) overload accepting braced init-list RHS
//
template <tuplish L>
constexpr auto compare3way(L const& l, tupl_view_t<L> r,auto...)noexcept
{ return compare3way(as_tupl_t(l), r); }
/*
equals(l,r): equality comparison of same-arity tuplish types
requires all equality comparable element types
*/
template <tuplish L, tuplish R>
constexpr bool equals(L const& l, R const& r) noexcept
requires types_all<is_equality_comparable_with,tupl_t<L>,tupl_t<R>>
{
return map(as_tupl_t(l), [&r](auto&...lh) noexcept {
return map(as_tupl_t(r), [&lh...](auto&...rh) noexcept
{
return (equal_to{}(lh,rh) && ...); // no short circuit, intended;
}); // use compare3way(l,r) == 0
});
}
// equals(lhs,rhs) overload accepting braced init-list RHS
//
template <tuplish L>
constexpr bool equals(L const& l, tupl_view_t<L> r, auto...) noexcept
{
return equals(as_tupl_t(l), r);
}
// equals(lhs,rhs) overload accepting empty braced init-list RHS
//
template <tuplish L>
constexpr bool equals(L const& l, std::true_type) noexcept
{
return map(as_tupl_t(l), [](auto&...ls) noexcept
{
return (equal_to{}(ls,{}) && ...); // no short circuit
});
}
//
// operator<=> overload, when possible and not defaulted in class
//
template <tuplish T>
requires (! types_all<is_member_default_3way,tupl_t<T>>
&& types_all<is_three_way_comparable,tupl_t<T>>)
constexpr auto operator<=>(T const& l,T const& r) noexcept
{
return compare3way(l,r);
}
//
// operator== overloads, when possible and not defaulted implicitly
// (Test that these overloads are not ambiguous)
//
template <tuplish T>
requires (! types_all<is_member_default_3way,tupl_t<T>>
&& types_all<is_three_way_comparable,tupl_t<T>>)
constexpr auto operator==(T const& l,T const& r) noexcept
{
return l <=> r == 0; // short circuits
}
//
template <tuplish T>
requires (! types_all<is_member_default_3way,tupl_t<T>>
&& ! types_all<is_three_way_comparable,tupl_t<T>>)
constexpr bool operator==(T const& l,T const& r) noexcept
{
return equals(l,r); // no short circuit
}
// SPDX-FileCopyrightText: 2023 The Lemuriad <opensource@lemurianlabs.com>
// SPDX-License-Identifier: BSL-1.0
/*
ties<E...> derives from tupl<E...> and adds operator=(rhs) overloads
1 operator=(same_ish<ties>) Replaces deleted default assignments
(the only non-const qualified operator=)
2 operator=(tuplish) const move or copy assigns from other tuplish
3a operator=({rvals}) const move-assigns from init-list of rvalues
3b operator=({lvals}) const copy-assigns from init-list of lvalues
4 operator=({}) const assign from empty init list, i.e. 'clear'
*/
template <typename...E> struct ties : tupl<E...>
{
// assign ties = ties replaces deleted default assign, non-const
template <same_ish<ties> Ties>
constexpr auto& operator=(Ties&& r)
noexcept(noexcept(assign_to{*this} = r))
requires tupl_tie<ties>
{ return assign_to{*this} = r; }
// assign ties = tuplish, allowing only non-narrowing conversions
template <tuplish T>
constexpr auto& operator=(T&& r) const
noexcept(noexcept(assign_to{*this} = r))
requires types_all<is_non_narrow_const_assignable,ties,tupl_t<T>>
{ return assign_to{*this} = r; }
// assign ties = tupl_move rvalue init list; move is better match
template <typename...>
constexpr auto& operator=(tupl_move_t<ties> r) const
noexcept(noexcept(assign_to{*this} = r))
requires (tupl_tie<ties> && move_assignable<E...>)
{ return assign_to{*this} = r; }
// assign ties = tupl_view lvalue init list; copy is worse match
template <typename A = tupl_view_t<ties>, typename B = A>
constexpr auto& operator=(A const& r) const
noexcept(noexcept(assign_to{*this} = r))
requires(std::same_as<A,B>&& tupl_tie<ties>&& copy_assignable<E...>)
{ return assign_to{*this} = r; }
// assign ties = {} empty list assignment
template<typename...>
constexpr auto& operator=(std::true_type) const
noexcept(noexcept(assign_to{*this} = std::true_type{}))
requires tupl_tie<ties>
{ return assign_to{*this} = std::true_type{}; }
};
// ties CTAD, deduces forwarding references
//
template <typename...E> ties(E&&...) -> ties<E&&...>;
//
// tie(a...) -> ties<decltype(a)&...>{a...}; rejects rvalue arguments
//
template <typename...T>
constexpr auto tie(T&...t) noexcept -> ties<T&...> const
{
return { t... };
}
//
// tie_fwd(a...) -> ties<decltype(a)...>{a...} const
// same as CTAD ties{a...} but const qualified
template <typename...T>
constexpr auto tie_fwd(T&&...t) noexcept -> ties<T&&...> const
{
return { (T&&)t... };
}
//
// getie<I...>(tupl) -> tie(get<I>(tupl)...)
//
template <unsigned...I>
constexpr auto getie(tuplish auto&& t) noexcept
-> ties<decltype(get<I>((decltype(t))t))...> const
requires ((I < tupl_size_v<decltype(t)>) && ...)
{ return {get<I>((decltype(t))t)...}; }
/*
vals<E...> derives from tupl<E...> and adds operator= overloads
1 operator=(tuplish); Non-narrowing copy-conversions from other tupl
2a operator=({rvals}); move-assigns from braced init-list of rvalues
2b operator=({lvals}); copy-assigns from braced init-list of lvalues
*/
template <typename...E> struct vals : tupl<E...>
{
// assign vals = tuplish, allowing only non-narrowing conversions
template <tuplish T>
constexpr auto& operator=(T&& r)
noexcept(noexcept(assign_to{*this} = (T&&)r))
requires (types_all<is_non_narrow_assignable,vals,tupl_t<T>>)
{ return assign_to{*this} = (T&&)r; }
// assign vals = tupl_move rvalue init list; move is better match
template <typename...>
constexpr auto& operator=(tupl_move_t<vals> r)
noexcept(noexcept(assign_to{*this} = (decltype(r)&&)r))
requires move_assignable<E...>
{ return assign_to{*this} = (decltype(r)&&)r; }
// assign tupl_val = tupl_view lvalue init list; copy is worse match
template <typename A = tupl_view_t<vals>, typename B = A>
constexpr auto& operator=(B r)
noexcept(noexcept(assign_to{*this} = r))
requires (std::same_as<A,B> && copy_assignable<E...>)
{ return assign_to{*this} = r; }
};
// vals CTAD
//
template <typename...E> vals(E const&...) -> vals<E...>;
/*
cmps<E...> derives from tupl<E...> and adds comparison operators
1 operator<=>(tuplish); compares any
2a operator=({rvals}); move-assigns from braced init-list of rvalues
2b operator=({lvals}); copy-assigns from braced init-list of lvalues
*/
template <typename...E> struct cmps : tupl<E...>
{
friend auto operator<=>(cmps const&,cmps const&) = default;
friend bool operator==(cmps const&,cmps const&) = default;
template <tuplish R>
friend constexpr auto operator<=>(cmps const& l, R const& r) noexcept
requires types_all<is_three_way_comparable_with,cmps,tupl_t<R>>
{
return compare3way(l,r);
}
template <tuplish R>
friend constexpr bool operator==(cmps const& l, R const& r) noexcept
requires types_all<is_equality_comparable_with,cmps,tupl_t<R>>
{
return equals(l,r);
}
template <tuplish R>
friend constexpr bool operator==(cmps, R const& r) noexcept
requires (sizeof...(E) == 0 && requires { equals(r,{}); })
{
return equals(r,{});
}
};
// cmps CTAD
//
template <typename...E> cmps(E const&...) -> cmps<tupl_view_t<E>...>;
// Generalized integer_sequence types
// INT_SEQ_MAP & SEQ_MAP platform-specific macros
// wrap integer-sequence generator builtins
//
#if defined(__clang__)
#define INT_SEQ_MAP(...)\
template<auto f>struct int_seq_map{template<class,int...I>__VA_ARGS__;};
#define SEQ_MAP()__make_integer_seq<int_seq_map<f>::template type,int,N>
#elif defined(__GNUG__)
#define INT_SEQ_MAP(...)\
namespace int_seq_map{template<auto f,int...I>__VA_ARGS__;}
#define SEQ_MAP()int_seq_map::type<f,__integer_pack(N)...>
#elif defined(_MSC_VER)
#define INT_SEQ_MAP(...)template<auto f>struct int_seq_map{\
template<class,int...I>struct ty{__VA_ARGS__;};};
#define SEQ_MAP()\
typename __make_integer_seq<int_seq_map<f>::template ty,int,N>::type;
#endif
// val_seq<T,v...> sequence of values of structural type T
//
template <typename T, T...v> struct val_seq {};
//template<auto f,int...I>
INT_SEQ_MAP(using type = val_seq<decltype(f(0)),f(I)...>)
#undef INT_SEQ_MAP
// val_seq_map<f,N> = val_seq of values f(I)... for I in [0..N)
//
template <auto f, int N> using val_seq_map = SEQ_MAP();
#undef SEQ_MAP
// int_seq<N,B,S> = int seq of N ints starting at B with step S
// B + S*[0..N)
//
template <int N, int B = 0, int S = 1>
using int_seq = val_seq_map<[](int i){return B+S*i;},N>;
// ij_t is a pair of int indices i,j
//
struct ij_t {int i,j;};
namespace impl {
// ij_map<s...> is a 2d index sequence of int pairs {i,j}
// where i indexes the s... and j indexes the jth element [0..s)
//
template <int...siz> struct ij_map
{
ij_t ij[(siz + ...)];
consteval ij_map() noexcept {
const int sizes[]{siz...};
for (int k=0, c=0; auto& [i,j] : ij) {
while (sizes[k] == c) {++k; c=0;}
i = k; j = c++;
}
}
consteval auto operator()(int s) const noexcept
{
return ij[s];
}
};
template <int...siz> requires ((siz+...+0)==0) struct ij_map<siz...>
{
consteval ij_t operator()(int) const noexcept {return{};}
};
} // impl
// ij_seq<s...> 2D index sequence of (s+...) ij_t int pairs
//
template <int...siz>
using ij_seq = val_seq_map<impl::ij_map<siz...>{},(siz+...+0)>;
// ctad_t<X,v> 'CTAD type'; the type that X{v{}} element 0 would have
// if X is a tuplish template id with value category CTAD
// and as if deduced array values initialize ok.
//
template <template<typename...>class X, typename v>
using ctad_t = copy_cvref_t<type_list_element_t<0,
decltype(X{std::declval<all_extents_removed_t<v>>()})>, v>;
namespace impl { // helpers to compute concatenated tupl types
template <tuplish...TL, auto...ij>
constexpr auto cat_t(val_seq<ij_t, ij...>)
-> tupl<type_list_element_t<ij.j,
type_list_element_t<ij.i, tupl<TL...>>>...>;
template <template<typename...>class X, tuplish...TL, auto...ij>
constexpr auto cat_ctad_t(val_seq<ij_t, ij...>)
-> X<ctad_t<X,type_list_element_t<ij.j,
type_list_element_t<ij.i, tupl<TL...>>>>...>;
} // impl
// cat_t<TL...> -> tupl<E...> concatenation type of tuplish element types
//
template <tuplish...TL>
using cat_t = decltype(impl::cat_t<std::remove_cvref_t<TL>...>
(ij_seq<tupl_size_v<TL>...>{}));
// cat_ctad_t<X,TL...> -> decltype( X{fwd<E>()...} )
// concatenation type of forwarded element types as deduced by X's CTAD
//
template <template<typename...>class X, tuplish...TL>
using cat_ctad_t = decltype(impl::cat_ctad_t<X,tupl_fwd_t<TL>...>
(ij_seq<tupl_size_v<TL>...>{}));
/*
tupl_init; tupl aggregate initialization function overloads
tupl_init<X,T...>(v...) -> X<T...>{v...};
Workaround 'maker function' to deal with possible array initializers.
The tupl elements are move-or-copy initialized from args v...
exploding any array initialization into its array element initializers
via integer_sequence expansion (so there's a size limit in practice)
and brace elision (missing-braces warnings on Clang are suppressed).
*/
template <template<typename...>class X = tupl, typename...T>
constexpr auto tupl_init(auto&&...v)
noexcept((is_nothrow_constructible_v<T,decltype(v)> && ...))
-> X<T...>
requires (sizeof...(T) == sizeof...(v))
{
if constexpr (requires {X<T...>{(decltype(v))v...};})
return {(decltype(v))v...}; // no arrays present, aggregate init
else // expand array elements in brace-elided aggregate initializers
return [&]<auto...ij>(val_seq<ij_t, ij...>)
noexcept((is_nothrow_constructible_v<T,decltype(v)> && ...))
-> X<T...>
{
NO_WARN_MISSING_BRACES(
return {flat_index(get<ij.i>(fwds{(decltype(v))v...}),ij.j)...};
)
}(ij_seq<flat_size<std::remove_cvref_t<T>>...>{});
}
/*
tupl_init(v...) -> tupl{v...}; with tupl CTAD
tupl_init<X>(v...) -> X{v...}; with template id X CTAD
*/
template <template<typename...>class X = tupl, typename...T>
constexpr auto tupl_init(auto&&...v) noexcept(
noexcept(tupl_init<X,ctad_t<X,decltype(v)>...>((decltype(v))v...)))
-> X<ctad_t<X,decltype(v)>...>
requires (sizeof...(T) == 0 && sizeof...(v) != 0 &&
requires{tupl_init<X,ctad_t<X,decltype(v)>...>((decltype(v))v...);})
{
return tupl_init<X,ctad_t<X,decltype(v)>...>((decltype(v))v...);
}
// cat(t...) concatenate tuplish t's preserving element types (no CTAD)
//
template <tuplish...TL>
constexpr auto cat(TL&&...tl) noexcept(
(std::is_nothrow_constructible_v<std::remove_cvref_t<TL>,TL&&> && ...))
-> cat_t<tupl_t<TL>...>
requires(std::is_constructible_v<std::remove_cvref_t<TL>,TL&&> && ...)
{
return [&]<auto...ij>(val_seq<ij_t,ij...>) noexcept(
(std::is_nothrow_constructible_v<std::remove_cvref_t<TL>,TL&&> && ...))
-> cat_t<tupl_t<TL>...>
{
return tupl_init<tupl,type_list_element_t<ij.j, std::remove_cvref_t<
type_list_element_t<ij.i, tupl<TL...>>>>...>
(get<ij.j>(get<ij.i>(fwds{(TL&&)tl...}))...);
}
(ij_seq<tupl_size_v<TL>...>{});
}
// cat<X>(t...) concatenate tuplish t's to X{v...} using X's CTAD
//
template <template<typename...>class X, tuplish...TL>
constexpr auto cat(TL&&...tl) noexcept(
(std::is_nothrow_constructible_v<cat_ctad_t<X,TL&&>,
apply_cvref_t<TL&&,cat_ctad_t<X,TL&&>>> && ...))
-> cat_ctad_t<X,TL&&...>
requires (std::is_constructible_v<cat_ctad_t<X,TL&&>,
apply_cvref_t<TL&&,cat_ctad_t<X,TL&&>>> && ...)
{
return [&]<auto...ij>(val_seq<ij_t,ij...>) noexcept(
(std::is_nothrow_constructible_v<cat_ctad_t<X,TL&&>,
apply_cvref_t<TL&&,cat_ctad_t<X,TL&&>>> && ...))
-> cat_ctad_t<X,TL&&...>
{
return tupl_init<X>(get<ij.j>(get<ij.i>(fwds{(TL&&)tl...}))...);
}
(ij_seq<tupl_size_v<TL>...>{});
}
#ifndef NAMESPACE_ID /* Configure your possibly-nested namespace-id */
# define NAMESPACE_ID lml
#endif
#ifndef NAMESPACE_ID_IS_EMPTY
# define NAMESPACE_ID_IS_EMPTY_(ID,ONE)ID##ONE
# define NAMESPACE_ID_IS_EMPTY(NS_ID)NAMESPACE_ID_IS_EMPTY_(NS_ID,0x01)
# if ! NAMESPACE_ID_IS_EMPTY(NAMESPACE_ID)
namespace NAMESPACE_ID {
# endif
#elif ! NAMESPACE_ID_IS_EMPTY(NAMESPACE_ID)
}
#undef NAMESPACE_ID_IS_EMPTY
#undef NAMESPACE_ID_IS_EMPTY_
#endif
#undef NO_WARN_MISSING_BRACES
#undef TYPEPACKEL
#undef NUA
#undef UNREACHABLE
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment