From 69c36f9b5f05ef64c00c40569d7e2410c960b44b Mon Sep 17 00:00:00 2001 From: Xiang Gao Date: Wed, 28 Jan 2026 22:33:58 -0800 Subject: [PATCH 1/4] Remove cartesian_product from any_check This update removes the cartesian_product function and introduces a more efficient any_check function that utilizes C++20 fold expressions. The new implementation checks if any elements in one or more tuples satisfy a given predicate, improving code clarity and performance. --- .../src/dynamic_type/type_traits.h | 85 ++++--------------- 1 file changed, 18 insertions(+), 67 deletions(-) diff --git a/lib/dynamic_type/src/dynamic_type/type_traits.h b/lib/dynamic_type/src/dynamic_type/type_traits.h index b195f2d028f..b20c711eec1 100644 --- a/lib/dynamic_type/src/dynamic_type/type_traits.h +++ b/lib/dynamic_type/src/dynamic_type/type_traits.h @@ -251,80 +251,31 @@ static_assert(!belongs_to); namespace dynamic_type { -// Take the cartesion product of two tuples. -// For example: -// cartesian_product((1, 2), (3, 4)) = ((1, 3), (1, 4), (2, 3), (2, 4)) -template -constexpr auto cartesian_product(Tuple t) { - return std::apply( - [](auto... ts) constexpr { - return std::make_tuple(std::make_tuple(ts)...); - }, - t); -} +// Check if any element(s) from the tuple(s) satisfy the predicate f. +// Uses C++20 fold expressions to efficiently test all combinations. +// +// Single tuple case: tests f(x) for each x in tuple +// Multiple tuples: tests f(x, y, ...) for all combinations across tuples -template -constexpr auto cartesian_product(Tuple1 first, OtherTuples... others) { - auto c_first = cartesian_product(first); - auto c_others = cartesian_product(others...); - // cat one item in c_first with all the items in c_others - auto cat_one_first_all_others = [c_others](auto first_item) { - return std::apply( - [first_item](auto... other_item) constexpr { - return std::make_tuple(std::tuple_cat(first_item, other_item)...); - }, - c_others); - }; +// Single tuple: any element satisfies f +template +constexpr bool any_check(Fun f, Tuple tuple) { return std::apply( - [cat_one_first_all_others](auto... first_items) constexpr { - return std::tuple_cat(cat_one_first_all_others(first_items)...); + [f](auto... elements) constexpr { + return (f(elements) || ...); }, - c_first); + tuple); } -// For example: - -static_assert( - cartesian_product(std::make_tuple(1.0, true)) == - std::make_tuple(std::make_tuple(1.0), std::make_tuple(true))); - -static_assert( - cartesian_product(std::make_tuple(1.0, true), std::make_tuple(2.0f, 4)) == - std::make_tuple( - std::make_tuple(1.0, 2.0f), - std::make_tuple(1.0, 4), - std::make_tuple(true, 2.0f), - std::make_tuple(true, 4))); - -static_assert( - cartesian_product( - std::make_tuple(1.0, true), - std::make_tuple(2.0f, 4), - std::make_tuple(std::size_t(0), nullptr)) == - std::make_tuple( - std::make_tuple(1.0, 2.0f, std::size_t(0)), - std::make_tuple(1.0, 2.0f, nullptr), - std::make_tuple(1.0, 4, std::size_t(0)), - std::make_tuple(1.0, 4, nullptr), - std::make_tuple(true, 2.0f, std::size_t(0)), - std::make_tuple(true, 2.0f, nullptr), - std::make_tuple(true, 4, std::size_t(0)), - std::make_tuple(true, 4, nullptr))); - -} // namespace dynamic_type - -namespace dynamic_type { - -// Can I find an x from tuple1 and a y from tuple12 such that f(x, y) is -// true? f(x, y) must be defined for all x in tuple1 and y in tuple2. -template -constexpr bool any_check(Fun f, Tuples... tuples) { - auto c = cartesian_product(tuples...); +// Multiple tuples: recursively check all combinations +template +constexpr bool any_check(Fun f, Tuple1 tuple1, Rest... rest) { return std::apply( - [f](auto... candidates) constexpr { - return any(std::apply(f, candidates)...); + [&](auto... xs) constexpr { + return (any_check([&](auto... args) constexpr { return f(xs, args...); }, + rest...) || ...); }, - c); + tuple1); } // For example: From a6650fed6826c30d4e1ac2de496c39bf43de1123 Mon Sep 17 00:00:00 2001 From: Xiang Gao Date: Wed, 28 Jan 2026 23:00:33 -0800 Subject: [PATCH 2/4] Refactor `any_check` to use C++20 fold expressions This update modernizes the `any_check` function by replacing its previous implementation with a more efficient version that leverages C++20 fold expressions. The new implementation simplifies the checks for predicate satisfaction across tuples, enhancing both clarity and performance. Additionally, several usages of `any_check` throughout the `dynamic_type` module have been updated to reflect this change. --- .../src/dynamic_type/dynamic_type.h | 129 ++++++++---------- .../src/dynamic_type/type_traits.h | 46 ------- 2 files changed, 54 insertions(+), 121 deletions(-) diff --git a/lib/dynamic_type/src/dynamic_type/dynamic_type.h b/lib/dynamic_type/src/dynamic_type/dynamic_type.h index 23ac02adde8..ccfcd8e5233 100644 --- a/lib/dynamic_type/src/dynamic_type/dynamic_type.h +++ b/lib/dynamic_type/src/dynamic_type/dynamic_type.h @@ -144,12 +144,11 @@ struct DynamicType { Containers::template is_candidate_type; template - static constexpr bool can_cast_to = any_check( - [](auto t) { - using From = typename decltype(t)::type; - return requires(From from) { + static constexpr bool can_cast_to = std::apply( + [](auto... ts) constexpr { + return ((requires(typename decltype(ts)::type from) { (T)(from); - }; + }) || ...); }, type_identities_as_tuple); @@ -425,14 +424,13 @@ struct DynamicType { } template - static constexpr bool has_square_bracket = any_check( - [](auto t) { - using T = typename decltype(t)::type; - return requires(T & tt, IndexT & idx) { + static constexpr bool has_square_bracket = std::apply( + [](auto... ts) constexpr { + return ((requires(typename decltype(ts)::type& tt, IndexT& idx) { { tt[idx] } -> std::same_as; - }; + }) || ...); }, type_identities_as_tuple); @@ -469,10 +467,9 @@ struct DynamicType { DEFINE_SQUARE_BRACKET_OPERATOR(const) #undef DEFINE_SQUARE_BRACKET_OPERATOR - static constexpr bool has_any_square_bracket = any_check( - [](auto t) { - using IndexT = typename decltype(t)::type; - return has_square_bracket; + static constexpr bool has_any_square_bracket = std::apply( + [](auto... ts) constexpr { + return (has_square_bracket || ...); }, type_identities_as_tuple); @@ -745,19 +742,14 @@ DEFINE_BINARY_OP(ge, >=, operator>=, bool, false); #undef DEFINE_BINARY_OP #define DEFINE_UNARY_OP(opname, op) \ - /*TODO: we should inline the definition of opname##_helper into requires,*/ \ - /*but I can only do this in C++20 */ \ - constexpr auto opname##_helper = [](auto x) constexpr { \ - using T = typename decltype(x)::type; \ - return requires(T t) { \ - op t; \ - }; \ - }; \ template \ - requires(is_dynamic_type_v>&& any_check( \ - opname##_helper, \ - std::decay_t< \ - DT>::type_identities_as_tuple)) inline constexpr decltype(auto) \ + requires(is_dynamic_type_v> && std::apply( \ + [](auto... ts) constexpr { \ + return ((requires(typename decltype(ts)::type t) { \ + op t; \ + }) || ...); \ + }, \ + std::decay_t
::type_identities_as_tuple)) inline constexpr decltype(auto) \ operator op(DT&& x) { \ return std::decay_t
::dispatch( \ [](auto&& x) -> decltype(auto) { \ @@ -783,18 +775,14 @@ DEFINE_UNARY_OP(lnot, !); // address of the dynamic type itself? template -auto star_defined_checker = [](auto t) { - using T = typename decltype(t)::type; - return requires(T & tt) { - { - *tt - } -> std::same_as; - }; -}; - -template -requires(is_dynamic_type_v
&& any_check( - star_defined_checker
, +requires(is_dynamic_type_v
&& std::apply( + [](auto... ts) constexpr { + return ((requires(typename decltype(ts)::type& tt) { + { + *tt + } -> std::same_as; + }) || ...); + }, DT::type_identities_as_tuple)) DT& operator*(const DT& x) { std::optional> ret = std::nullopt; @@ -815,19 +803,15 @@ operator*(const DT& x) { } // Printing -// TODO: we should inline the definition of can_print into requires, but I can -// only do this in C++20 -constexpr auto can_print = [](auto x) constexpr { - using T = typename decltype(x)::type; - return requires(std::ostream & os, T && t) { - { - os << t - } -> std::same_as; - }; -}; template -requires(is_dynamic_type_v
&& any_check( - can_print, +requires(is_dynamic_type_v
&& std::apply( + [](auto... ts) constexpr { + return ((requires(std::ostream& os, typename decltype(ts)::type&& t) { + { + os << t + } -> std::same_as; + }) || ...); + }, DT::type_identities_as_tuple)) std::ostream& operator<<(std::ostream& os, const DT& dt) { bool printed = false; @@ -850,19 +834,16 @@ operator<<(std::ostream& os, const DT& dt) { } #define DEFINE_LEFT_PPMM(opname, op) \ - /*TODO: we should inline the definition of opname##_helper into requires,*/ \ - /*but I can only do this in C++20 */ \ - constexpr auto opname##_helper = [](auto x) constexpr { \ - using X = typename decltype(x)::type; \ - return requires(X & xx) { \ - { \ - op xx \ - } -> std::same_as; \ - }; \ - }; \ template \ - requires(is_dynamic_type_v
&& any_check( \ - opname##_helper, DT::type_identities_as_tuple)) inline constexpr DT& \ + requires(is_dynamic_type_v
&& std::apply( \ + [](auto... ts) constexpr { \ + return ((requires(typename decltype(ts)::type& xx) { \ + { \ + op xx \ + } -> std::same_as; \ + }) || ...); \ + }, \ + DT::type_identities_as_tuple)) inline constexpr DT& \ operator op(DT & x) { \ bool computed = false; \ DT::for_all_types([&computed, &x](auto _) { \ @@ -893,20 +874,18 @@ DEFINE_LEFT_PPMM(lmm, --); #undef DEFINE_LEFT_PPMM #define DEFINE_RIGHT_PPMM(opname, op) \ - /*TODO: we should inline the definition of opname##_helper into requires,*/ \ - /*but I can only do this in C++20 */ \ - template \ - constexpr auto opname##_helper = [](auto x) constexpr { \ - using X = typename decltype(x)::type; \ - if constexpr (requires(X & xx) { xx op; }) { \ - using ResultType = decltype(std::declval() op); \ - return std::is_constructible_v; \ - } \ - return false; \ - }; \ template \ - requires(is_dynamic_type_v
&& any_check( \ - opname##_helper, \ + requires(is_dynamic_type_v
&& std::apply( \ + [](auto... ts) constexpr { \ + return (([]{ \ + using X = typename decltype(ts)::type; \ + if constexpr (requires(X& xx) { xx op; }) { \ + using ResultType = decltype(std::declval() op); \ + return std::is_constructible_v; \ + } \ + return false; \ + }()) || ...); \ + }, \ DT::type_identities_as_tuple)) inline constexpr DT \ operator op(DT& x, int) { \ DT ret; \ diff --git a/lib/dynamic_type/src/dynamic_type/type_traits.h b/lib/dynamic_type/src/dynamic_type/type_traits.h index b20c711eec1..b05ade49fad 100644 --- a/lib/dynamic_type/src/dynamic_type/type_traits.h +++ b/lib/dynamic_type/src/dynamic_type/type_traits.h @@ -249,52 +249,6 @@ static_assert(!belongs_to); } // namespace dynamic_type -namespace dynamic_type { - -// Check if any element(s) from the tuple(s) satisfy the predicate f. -// Uses C++20 fold expressions to efficiently test all combinations. -// -// Single tuple case: tests f(x) for each x in tuple -// Multiple tuples: tests f(x, y, ...) for all combinations across tuples - -// Single tuple: any element satisfies f -template -constexpr bool any_check(Fun f, Tuple tuple) { - return std::apply( - [f](auto... elements) constexpr { - return (f(elements) || ...); - }, - tuple); -} - -// Multiple tuples: recursively check all combinations -template -constexpr bool any_check(Fun f, Tuple1 tuple1, Rest... rest) { - return std::apply( - [&](auto... xs) constexpr { - return (any_check([&](auto... args) constexpr { return f(xs, args...); }, - rest...) || ...); - }, - tuple1); -} - -// For example: -static_assert( - any_check([](auto x) constexpr { return x > 0; }, std::make_tuple(1, -1))); -static_assert(!any_check( - [](auto x) constexpr { return x > 0; }, - std::make_tuple(-2, -1))); - -static_assert(any_check( - [](auto x, auto y) constexpr { return (x + y) > 0; }, - std::make_tuple(2.0, 1), - std::make_tuple(-2, -1))); -static_assert(!any_check( - [](auto x, auto y) constexpr { return (x + y) > 0; }, - std::make_tuple(1.0, 1), - std::make_tuple(-2, -1))); - -} // namespace dynamic_type namespace dynamic_type { From 04bad4318006bdf1913b5e685e5a81be5e75f103 Mon Sep 17 00:00:00 2001 From: Xiang Date: Wed, 28 Jan 2026 23:46:55 -0800 Subject: [PATCH 3/4] format --- .../src/dynamic_type/dynamic_type.h | 169 +++++++++--------- .../src/dynamic_type/type_traits.h | 1 - 2 files changed, 89 insertions(+), 81 deletions(-) diff --git a/lib/dynamic_type/src/dynamic_type/dynamic_type.h b/lib/dynamic_type/src/dynamic_type/dynamic_type.h index ccfcd8e5233..3a9ec6f3b8e 100644 --- a/lib/dynamic_type/src/dynamic_type/dynamic_type.h +++ b/lib/dynamic_type/src/dynamic_type/dynamic_type.h @@ -146,9 +146,8 @@ struct DynamicType { template static constexpr bool can_cast_to = std::apply( [](auto... ts) constexpr { - return ((requires(typename decltype(ts)::type from) { - (T)(from); - }) || ...); + return ( + (requires(typename decltype(ts)::type from) { (T)(from); }) || ...); }, type_identities_as_tuple); @@ -426,11 +425,13 @@ struct DynamicType { template static constexpr bool has_square_bracket = std::apply( [](auto... ts) constexpr { - return ((requires(typename decltype(ts)::type& tt, IndexT& idx) { - { - tt[idx] - } -> std::same_as; - }) || ...); + return ( + (requires(typename decltype(ts)::type & tt, IndexT & idx) { + { + tt[idx] + } -> std::same_as; + }) || + ...); }, type_identities_as_tuple); @@ -741,24 +742,23 @@ DEFINE_BINARY_OP(ge, >=, operator>=, bool, false); #undef DEFINE_BINARY_OP -#define DEFINE_UNARY_OP(opname, op) \ - template \ - requires(is_dynamic_type_v> && std::apply( \ - [](auto... ts) constexpr { \ - return ((requires(typename decltype(ts)::type t) { \ - op t; \ - }) || ...); \ - }, \ - std::decay_t
::type_identities_as_tuple)) inline constexpr decltype(auto) \ - operator op(DT&& x) { \ - return std::decay_t
::dispatch( \ - [](auto&& x) -> decltype(auto) { \ - using X = std::decay_t; \ - if constexpr (requires(X && xx) { op xx; }) { \ - return op std::forward(x); \ - } \ - }, \ - std::forward
(x)); \ +#define DEFINE_UNARY_OP(opname, op) \ + template \ + requires(is_dynamic_type_v>&& std::apply( \ + [](auto... ts) constexpr { \ + return ((requires(typename decltype(ts)::type t) { op t; }) || ...); \ + }, \ + std::decay_t< \ + DT>::type_identities_as_tuple)) inline constexpr decltype(auto) \ + operator op(DT&& x) { \ + return std::decay_t
::dispatch( \ + [](auto&& x) -> decltype(auto) { \ + using X = std::decay_t; \ + if constexpr (requires(X && xx) { op xx; }) { \ + return op std::forward(x); \ + } \ + }, \ + std::forward
(x)); \ } DEFINE_UNARY_OP(pos, +); @@ -775,13 +775,15 @@ DEFINE_UNARY_OP(lnot, !); // address of the dynamic type itself? template -requires(is_dynamic_type_v
&& std::apply( +requires(is_dynamic_type_v
&& std::apply( [](auto... ts) constexpr { - return ((requires(typename decltype(ts)::type& tt) { - { - *tt - } -> std::same_as; - }) || ...); + return ( + (requires(typename decltype(ts)::type & tt) { + { + *tt + } -> std::same_as; + }) || + ...); }, DT::type_identities_as_tuple)) DT& operator*(const DT& x) { @@ -804,13 +806,15 @@ operator*(const DT& x) { // Printing template -requires(is_dynamic_type_v
&& std::apply( +requires(is_dynamic_type_v
&& std::apply( [](auto... ts) constexpr { - return ((requires(std::ostream& os, typename decltype(ts)::type&& t) { - { - os << t - } -> std::same_as; - }) || ...); + return ( + (requires(std::ostream & os, typename decltype(ts)::type && t) { + { + os << t + } -> std::same_as; + }) || + ...); }, DT::type_identities_as_tuple)) std::ostream& operator<<(std::ostream& os, const DT& dt) { @@ -833,39 +837,41 @@ operator<<(std::ostream& os, const DT& dt) { return os; } -#define DEFINE_LEFT_PPMM(opname, op) \ - template \ - requires(is_dynamic_type_v
&& std::apply( \ - [](auto... ts) constexpr { \ - return ((requires(typename decltype(ts)::type& xx) { \ - { \ - op xx \ - } -> std::same_as; \ - }) || ...); \ - }, \ - DT::type_identities_as_tuple)) inline constexpr DT& \ - operator op(DT & x) { \ - bool computed = false; \ - DT::for_all_types([&computed, &x](auto _) { \ - using Type = typename decltype(_)::type; \ - if constexpr (requires(Type & t) { \ - { \ - op t \ - } -> std::same_as; \ - }) { \ - if (x.template is()) { \ - op x.template as(); \ - computed = true; \ - } \ - } \ - }); \ - DYNAMIC_TYPE_CHECK( \ - computed, \ - "Cannot compute ", \ - #op, \ - x.type().name(), \ - " : incompatible type"); \ - return x; \ +#define DEFINE_LEFT_PPMM(opname, op) \ + template \ + requires(is_dynamic_type_v
&& std::apply( \ + [](auto... ts) constexpr { \ + return ( \ + (requires(typename decltype(ts)::type & xx) { \ + { \ + op xx \ + } -> std::same_as; \ + }) || \ + ...); \ + }, \ + DT::type_identities_as_tuple)) inline constexpr DT& \ + operator op(DT & x) { \ + bool computed = false; \ + DT::for_all_types([&computed, &x](auto _) { \ + using Type = typename decltype(_)::type; \ + if constexpr (requires(Type & t) { \ + { \ + op t \ + } -> std::same_as; \ + }) { \ + if (x.template is()) { \ + op x.template as(); \ + computed = true; \ + } \ + } \ + }); \ + DYNAMIC_TYPE_CHECK( \ + computed, \ + "Cannot compute ", \ + #op, \ + x.type().name(), \ + " : incompatible type"); \ + return x; \ } DEFINE_LEFT_PPMM(lpp, ++); @@ -875,16 +881,19 @@ DEFINE_LEFT_PPMM(lmm, --); #define DEFINE_RIGHT_PPMM(opname, op) \ template \ - requires(is_dynamic_type_v
&& std::apply( \ + requires(is_dynamic_type_v
&& std::apply( \ [](auto... ts) constexpr { \ - return (([]{ \ - using X = typename decltype(ts)::type; \ - if constexpr (requires(X& xx) { xx op; }) { \ - using ResultType = decltype(std::declval() op); \ - return std::is_constructible_v; \ - } \ - return false; \ - }()) || ...); \ + return ( \ + ([] { \ + using X = typename decltype(ts)::type; \ + if constexpr (requires(X & xx) { xx op; }) { \ + using ResultType = decltype(std::declval() op); \ + return std:: \ + is_constructible_v; \ + } \ + return false; \ + }()) || \ + ...); \ }, \ DT::type_identities_as_tuple)) inline constexpr DT \ operator op(DT& x, int) { \ diff --git a/lib/dynamic_type/src/dynamic_type/type_traits.h b/lib/dynamic_type/src/dynamic_type/type_traits.h index b05ade49fad..67dd6607ed0 100644 --- a/lib/dynamic_type/src/dynamic_type/type_traits.h +++ b/lib/dynamic_type/src/dynamic_type/type_traits.h @@ -249,7 +249,6 @@ static_assert(!belongs_to); } // namespace dynamic_type - namespace dynamic_type { // Check if all the types in the tuple are the same. If the tuple is empty, or From 15bd59a4d4143095c0e28935cac678d024dafdfb Mon Sep 17 00:00:00 2001 From: Xiang Date: Thu, 29 Jan 2026 00:30:32 -0800 Subject: [PATCH 4/4] cleanup --- .../src/dynamic_type/dynamic_type.h | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/lib/dynamic_type/src/dynamic_type/dynamic_type.h b/lib/dynamic_type/src/dynamic_type/dynamic_type.h index 3a9ec6f3b8e..bf0d08de5af 100644 --- a/lib/dynamic_type/src/dynamic_type/dynamic_type.h +++ b/lib/dynamic_type/src/dynamic_type/dynamic_type.h @@ -604,14 +604,15 @@ struct DynamicType { // arguments. I believe it is doable, but I decide to leave it for future. }; -template -struct is_dynamic_type : std::false_type {}; +template +inline constexpr bool is_dynamic_type_v = false; -template -struct is_dynamic_type> : std::true_type {}; +template +inline constexpr bool is_dynamic_type_v> = true; +// A DynamicType, allowing cv/ref qualifiers on the deduced type. template -constexpr bool is_dynamic_type_v = is_dynamic_type::value; +concept DynamicTypeLike = is_dynamic_type_v>; #define DEFINE_BINARY_OP(opname, op, func_name, return_type, check_existence) \ template \ @@ -743,15 +744,15 @@ DEFINE_BINARY_OP(ge, >=, operator>=, bool, false); #undef DEFINE_BINARY_OP #define DEFINE_UNARY_OP(opname, op) \ - template \ - requires(is_dynamic_type_v>&& std::apply( \ + template \ + requires(std::apply( \ [](auto... ts) constexpr { \ return ((requires(typename decltype(ts)::type t) { op t; }) || ...); \ }, \ - std::decay_t< \ + std::remove_cvref_t< \ DT>::type_identities_as_tuple)) inline constexpr decltype(auto) \ operator op(DT&& x) { \ - return std::decay_t
::dispatch( \ + return std::remove_cvref_t
::dispatch( \ [](auto&& x) -> decltype(auto) { \ using X = std::decay_t; \ if constexpr (requires(X && xx) { op xx; }) { \ @@ -774,8 +775,8 @@ DEFINE_UNARY_OP(lnot, !); // an alternative. Also, if we overloaded the operator&, how can we get the // address of the dynamic type itself? -template -requires(is_dynamic_type_v
&& std::apply( +template +requires(std::apply( [](auto... ts) constexpr { return ( (requires(typename decltype(ts)::type & tt) { @@ -805,8 +806,8 @@ operator*(const DT& x) { } // Printing -template -requires(is_dynamic_type_v
&& std::apply( +template +requires(std::apply( [](auto... ts) constexpr { return ( (requires(std::ostream & os, typename decltype(ts)::type && t) { @@ -838,8 +839,8 @@ operator<<(std::ostream& os, const DT& dt) { } #define DEFINE_LEFT_PPMM(opname, op) \ - template \ - requires(is_dynamic_type_v
&& std::apply( \ + template \ + requires(std::apply( \ [](auto... ts) constexpr { \ return ( \ (requires(typename decltype(ts)::type & xx) { \ @@ -880,8 +881,8 @@ DEFINE_LEFT_PPMM(lmm, --); #undef DEFINE_LEFT_PPMM #define DEFINE_RIGHT_PPMM(opname, op) \ - template \ - requires(is_dynamic_type_v
&& std::apply( \ + template \ + requires(std::apply( \ [](auto... ts) constexpr { \ return ( \ ([] { \ @@ -925,12 +926,12 @@ DEFINE_RIGHT_PPMM(rmm, --); #undef DEFINE_RIGHT_PPMM -#define DEFINE_ASSIGNMENT_OP(op, assign_op) \ - template \ - requires(is_dynamic_type_v
&& (requires(DT dt, T t) { \ - dt op t; \ - })) inline constexpr DT& operator assign_op(DT & x, const T & y) { \ - return x = x op y; \ +#define DEFINE_ASSIGNMENT_OP(op, assign_op) \ + template \ + requires(requires(DT dt, T t) { \ + dt op t; \ + }) inline constexpr DT& operator assign_op(DT & x, const T & y) { \ + return x = x op y; \ } DEFINE_ASSIGNMENT_OP(+, +=); @@ -950,7 +951,7 @@ DEFINE_ASSIGNMENT_OP(>>, >>=); // Check that, whether there exist two different types T and U, where both T and // U are contained in the type list of dynamic type DT, and T == U is defined. -template +template constexpr bool has_cross_type_equality = any(remove_void_from_tuple(DT::for_all_types([](auto t) { using T = typename decltype(t)::type;