Last active
June 26, 2023 17:32
-
-
Save willwray/32ea631b5b6d59a0c9d80573209560e7 to your computer and use it in GitHub Desktop.
Amalgamated header for tupl library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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