From c95468b89031de9a23cf266a63a528af39c162ae Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Wed, 15 Jul 2026 15:46:11 -0600 Subject: [PATCH] :art: Adjust tuple concepts Problem: - Algorithms like `all_of` don't work with `std::tuple` or `std::array`. Solution: - Adjust concepts to separate `has_tuple_protocol` and `has_array_protocol` (note: `has_array_protocol` subsumes `has_tuple_protocol`. - Allow tuple algorithms to work with `std::tuple` by constraining on `has_tuple_protocol` instead of `tuplelike` (which means just `stdx::tuple`). - Add more `unrolled_*` algorithms to deal with those use cases for arrays and spans. --- include/stdx/concepts.hpp | 97 +++++++++++++++---------- include/stdx/span.hpp | 6 ++ include/stdx/tuple.hpp | 51 +------------ include/stdx/tuple_algorithms.hpp | 109 +++++++++++++++++++++++----- include/stdx/tuple_destructure.hpp | 9 +++ include/stdx/type_traits.hpp | 28 +++++++ include/stdx/utility.hpp | 8 ++ test/fail/CMakeLists.txt | 1 + test/fail/non_unrolled_for_each.cpp | 10 +++ test/indexed_tuple.cpp | 5 -- test/tuple.cpp | 17 ----- test/tuple_algorithms.cpp | 108 +++++++++++++++++++++++++++ 12 files changed, 319 insertions(+), 130 deletions(-) create mode 100644 test/fail/non_unrolled_for_each.cpp diff --git a/include/stdx/concepts.hpp b/include/stdx/concepts.hpp index 3cd60e7..1e1eb23 100644 --- a/include/stdx/concepts.hpp +++ b/include/stdx/concepts.hpp @@ -2,6 +2,8 @@ #include +#include + #if __has_include() #include #endif @@ -9,16 +11,15 @@ #if __cpp_lib_concepts < 202002L #include -#include #include namespace stdx { inline namespace v1 { template -concept integral = std::is_integral_v; +concept floating_point = std::is_floating_point_v; template -concept floating_point = std::is_floating_point_v; +concept integral = std::is_integral_v; template concept signed_integral = integral and std::is_signed_v; @@ -43,16 +44,6 @@ concept same_as_helper = std::is_same_v; template concept same_as = detail::same_as_helper and detail::same_as_helper; -template -constexpr auto same_any = (... or same_as); - -template -constexpr auto same_none = not same_any; - -template -concept same_as_unqualified = - same_as, std::remove_cvref_t>; - template concept equality_comparable = requires(T const &t) { { t == t } -> same_as; @@ -91,21 +82,6 @@ concept boolean_testable = boolean_testable_impl and requires(B &&b) { template concept predicate = invocable and detail::boolean_testable>; - -template -concept callable = is_callable_v; - -template typename TypeTrait> -concept has_trait = TypeTrait::value; - -template -concept structural = is_structural_v; - -template -concept complete = is_complete_v; - -template -concept same_template_as = is_same_template_v; } // namespace v1 } // namespace stdx @@ -130,17 +106,30 @@ using std::totally_ordered; using std::invocable; using std::predicate; +} // namespace v1 +} // namespace stdx -template -concept callable = is_callable_v; +#endif -template typename TypeTrait> -concept has_trait = TypeTrait::value; +namespace stdx { +inline namespace v1 { template concept same_as_unqualified = same_as, std::remove_cvref_t>; +template +constexpr auto same_any = (... or same_as); + +template +constexpr auto same_none = not same_any; + +template +concept callable = is_callable_v; + +template typename TypeTrait> +concept has_trait = TypeTrait::value; + template concept structural = is_structural_v; @@ -150,12 +139,44 @@ concept complete = is_complete_v; template concept same_template_as = is_same_template_v; -template -constexpr auto same_any = (... or same_as); +template +concept tuple_comparable = requires { typename T::common_tuple_comparable; }; + +template +concept tuplelike = requires { typename std::remove_cvref_t::is_tuple; }; + +namespace detail { +template +concept has_vacuous_tuple_protocol = requires { + { + tuple_size_v> + } -> std::same_as; +}; +template +concept is_vacuous_tuple = tuple_size_v> == 0; + +constexpr inline auto concept_try_get = [](auto &x) -> decltype(auto) { + using std::get; + return get<0>(x); +}; + +template +concept has_get_protocol = requires(T &t) { + { + concept_try_get(t) + } -> same_as_unqualified>>; +}; +} // namespace detail + +template +concept has_tuple_protocol = detail::has_vacuous_tuple_protocol and + (detail::is_vacuous_tuple or + detail::has_get_protocol>); + +template +concept has_array_protocol = has_tuple_protocol and requires { + typename std::remove_cvref_t::value_type; +}; -template -constexpr auto same_none = not same_any; } // namespace v1 } // namespace stdx - -#endif diff --git a/include/stdx/span.hpp b/include/stdx/span.hpp index 4671d08..4f6f1e7 100644 --- a/include/stdx/span.hpp +++ b/include/stdx/span.hpp @@ -281,5 +281,11 @@ constexpr auto ct_capacity_v> = N; template constexpr auto ct_capacity_v> = detail::ct_capacity_fail>{}; + +template +struct tuple_element> { + static_assert(N != dynamic_extent and I < N); + using type = T; +}; } // namespace v1 } // namespace stdx diff --git a/include/stdx/tuple.hpp b/include/stdx/tuple.hpp index 73f0617..59ec39f 100644 --- a/include/stdx/tuple.hpp +++ b/include/stdx/tuple.hpp @@ -1,10 +1,10 @@ #pragma once +#include #include #include #include -#include #include #include #include @@ -420,59 +420,10 @@ tuple_impl(Ts...) -> tuple_impl, index_function_list<>, Ts...>; } // namespace detail -template constexpr auto tuple_size_v = T::size(); -template -constexpr auto tuple_size_v> = N; -template -constexpr auto tuple_size_v> = std::size_t{2}; -template -constexpr auto tuple_size_v> = std::size_t{N}; - -template -concept tuple_comparable = requires { typename T::common_tuple_comparable; }; - -template -concept tuplelike = requires { typename std::remove_cvref_t::is_tuple; }; - -template struct tuple_element; - template struct tuple_element { using type = typename T::template element_t; }; -template -struct tuple_element> { - using type = T; -}; - -template -struct tuple_element> { - using type = nth_t; -}; - -template -using tuple_element_t = typename tuple_element::type; - -namespace detail { -template -concept has_vacuous_tuple_protocol = requires { - { - tuple_size_v> - } -> std::same_as; -}; -template -concept is_vacuous_tuple = tuple_size_v> == 0; -} // namespace detail - -template -concept has_tuple_protocol = - detail::has_vacuous_tuple_protocol and - (detail::is_vacuous_tuple or requires(T &t) { - { - get<0>(t) - } -> std::same_as> &>; - }); - template class tuple : public detail::tuple_impl, detail::index_function_list<>, Ts...> { diff --git a/include/stdx/tuple_algorithms.hpp b/include/stdx/tuple_algorithms.hpp index fbc65a5..74d8fa0 100644 --- a/include/stdx/tuple_algorithms.hpp +++ b/include/stdx/tuple_algorithms.hpp @@ -1,7 +1,9 @@ #pragma once +#include #include #include +#include #include #include @@ -95,23 +97,27 @@ template [[nodiscard]] constexpr auto tuple_cat(Ts &&...ts) { } } -template +template [[nodiscard]] constexpr auto tuple_cons(T &&t, Tup &&tup) { using tuple_t = std::remove_cvref_t; return [&](std::index_sequence) { - return stdx::tuple, - stdx::tuple_element_t...>{ - std::forward(t), std::forward(tup)[index]...}; + using std::get; + using new_tuple_t = + boost::mp11::mp_push_front>; + return new_tuple_t{std::forward(t), + get(std::forward(tup))...}; }(std::make_index_sequence>{}); } -template +template [[nodiscard]] constexpr auto tuple_snoc(Tup &&tup, T &&t) { using tuple_t = std::remove_cvref_t; return [&](std::index_sequence) { - return stdx::tuple..., - std::remove_cvref_t>{ - std::forward(tup)[index]..., std::forward(t)}; + using std::get; + using new_tuple_t = + boost::mp11::mp_push_back>; + return new_tuple_t{get(std::forward(tup))..., + std::forward(t)}; }(std::make_index_sequence>{}); } @@ -186,6 +192,7 @@ constexpr auto transform(Op &&op, Ts &&...ts) { }(std::make_index_sequence>{}); } +namespace detail { template constexpr auto unrolled_for_each(Op &&op, Ts &&...ts) -> Op { [&](std::index_sequence) { @@ -193,10 +200,21 @@ constexpr auto unrolled_for_each(Op &&op, Ts &&...ts) -> Op { }(std::make_index_sequence>{}); return op; } +} // namespace detail -template +template constexpr auto for_each(Op &&op, Ts &&...ts) -> Op { - return unrolled_for_each(std::forward(op), std::forward(ts)...); + static_assert((... or not has_array_protocol), + "Calling for_each on array-like type: use unrolled_for_each " + "if you really want this."); + return detail::unrolled_for_each(std::forward(op), + std::forward(ts)...); +} + +template +constexpr auto unrolled_for_each(Op &&op, Ts &&...ts) -> Op { + return detail::unrolled_for_each(std::forward(op), + std::forward(ts)...); } namespace detail { @@ -204,7 +222,6 @@ template constexpr auto invoke_with_idx_at(auto &&op, Ts &&...ts) -> decltype(auto) { return op.template operator()(get(std::forward(ts))...); } -} // namespace detail template constexpr auto unrolled_enumerate(Op &&op, Ts &&...ts) -> Op { @@ -213,28 +230,81 @@ constexpr auto unrolled_enumerate(Op &&op, Ts &&...ts) -> Op { }(std::make_index_sequence>{}); return op; } +} // namespace detail -template +template constexpr auto enumerate(Op &&op, Ts &&...ts) -> Op { - return unrolled_enumerate(std::forward(op), std::forward(ts)...); + static_assert( + (... or not has_array_protocol), + "Calling enumerate on array-like type: use unrolled_enumerate " + "if you really want this."); + return detail::unrolled_enumerate(std::forward(op), + std::forward(ts)...); } -template -constexpr auto all_of(F &&f, Ts &&...ts) -> bool { +template +constexpr auto unrolled_enumerate(Op &&op, Ts &&...ts) -> Op { + return detail::unrolled_enumerate(std::forward(op), + std::forward(ts)...); +} + +namespace detail { +template +constexpr auto unrolled_all_of(F &&f, Ts &&...ts) -> bool { return [&](std::index_sequence) { return (... and detail::invoke_at(f, std::forward(ts)...)); }(std::make_index_sequence>{}); } +} // namespace detail -template -constexpr auto any_of(F &&f, Ts &&...ts) -> bool { +template +constexpr auto all_of(F &&f, Ts &&...ts) -> bool { + static_assert((... or not has_array_protocol), + "Calling all_of on array-like type: use unrolled_all_of " + "if you really want this."); + return detail::unrolled_all_of(std::forward(f), std::forward(ts)...); +} + +template +constexpr auto unrolled_all_of(F &&f, Ts &&...ts) -> bool { + return detail::unrolled_all_of(std::forward(f), std::forward(ts)...); +} + +namespace detail { +template +constexpr auto unrolled_any_of(F &&f, Ts &&...ts) -> bool { return [&](std::index_sequence) { return (... or detail::invoke_at(f, std::forward(ts)...)); }(std::make_index_sequence>{}); } +} // namespace detail + +template +constexpr auto any_of(F &&f, Ts &&...ts) -> bool { + static_assert((... or not has_array_protocol), + "Calling any_of on array-like type: use unrolled_any_of " + "if you really want this."); + return detail::unrolled_any_of(std::forward(f), std::forward(ts)...); +} -template constexpr auto none_of(Ts &&...ts) -> bool { - return not any_of(std::forward(ts)...); +template +constexpr auto unrolled_any_of(F &&f, Ts &&...ts) -> bool { + return detail::unrolled_any_of(std::forward(f), std::forward(ts)...); +} + +template +constexpr auto none_of(F &&f, Ts &&...ts) -> bool { + static_assert((... or not has_array_protocol), + "Calling none_of on array-like type: use unrolled_none_of " + "if you really want this."); + return not detail::unrolled_any_of(std::forward(f), + std::forward(ts)...); +} + +template +constexpr auto unrolled_none_of(F &&f, Ts &&...ts) -> bool { + return not detail::unrolled_any_of(std::forward(f), + std::forward(ts)...); } namespace detail { @@ -280,7 +350,6 @@ template