Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_DECOMPOSES_TO_HPP
11 : #define BOOST_CAPY_DECOMPOSES_TO_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 :
15 : #include <system_error>
16 : #include <concepts>
17 : #include <cstddef>
18 : #include <tuple>
19 : #include <type_traits>
20 : #include <utility>
21 :
22 : namespace boost {
23 : namespace capy {
24 : namespace detail {
25 :
26 : struct any_type
27 : {
28 : template <typename T>
29 : constexpr operator T() const noexcept;
30 : };
31 :
32 : template <typename T, std::size_t N>
33 : concept is_tuple_n = requires {
34 : std::tuple_size<std::remove_cvref_t<T>>::value;
35 : } && std::tuple_size<std::remove_cvref_t<T>>::value == N;
36 :
37 : // clang-format off
38 : template <typename T>
39 : concept is_decomposable_1 =
40 : (std::is_aggregate_v<std::remove_cvref_t<T>> &&
41 : requires { std::remove_cvref_t<T>{ any_type{} }; } &&
42 : !requires { std::remove_cvref_t<T>{ any_type{}, any_type{} }; }
43 : ) || is_tuple_n<T, 1>;
44 :
45 : template <typename T>
46 : concept is_decomposable_2 =
47 : (std::is_aggregate_v<std::remove_cvref_t<T>> &&
48 : requires { std::remove_cvref_t<T>{ any_type{}, any_type{} }; } &&
49 : !requires { std::remove_cvref_t<T>{ any_type{}, any_type{}, any_type{} }; }
50 : ) || is_tuple_n<T, 2>;
51 :
52 : template <typename T>
53 : concept is_decomposable_3 =
54 : (std::is_aggregate_v<std::remove_cvref_t<T>> &&
55 : requires { std::remove_cvref_t<T>{ any_type{}, any_type{}, any_type{} }; } &&
56 : !requires { std::remove_cvref_t<T>{ any_type{}, any_type{}, any_type{}, any_type{} }; }
57 : ) || is_tuple_n<T, 3>;
58 :
59 : template <typename T>
60 : concept is_decomposable_4 =
61 : (std::is_aggregate_v<std::remove_cvref_t<T>> &&
62 : requires { std::remove_cvref_t<T>{ any_type{}, any_type{}, any_type{}, any_type{} }; } &&
63 : !requires { std::remove_cvref_t<T>{ any_type{}, any_type{}, any_type{}, any_type{}, any_type{} }; }
64 : ) || is_tuple_n<T, 4>;
65 :
66 : // clang-format on
67 :
68 : template <is_decomposable_1 T>
69 : auto decomposed_types(T&& t)
70 : {
71 : auto [v0] = t;
72 : return std::make_tuple(v0);
73 : }
74 :
75 : template <is_decomposable_2 T>
76 : auto decomposed_types(T&& t)
77 : {
78 : auto [v0, v1] = t;
79 : return std::make_tuple(v0, v1);
80 : }
81 :
82 : template <is_decomposable_3 T>
83 : auto decomposed_types(T&& t)
84 : {
85 : auto [v0, v1, v2] = t;
86 : return std::make_tuple(v0, v1, v2);
87 : }
88 :
89 : template <is_decomposable_4 T>
90 : auto decomposed_types(T&& t)
91 : {
92 : auto [v0, v1, v2, v3] = t;
93 : return std::make_tuple(v0, v1, v2, v3);
94 : }
95 :
96 : template <class T>
97 : std::tuple<> decomposed_types(T&&)
98 : {
99 : return {};
100 : }
101 :
102 : template<typename T>
103 0 : auto get_awaiter(T&& t)
104 : {
105 : if constexpr (requires { std::forward<T>(t).operator co_await(); })
106 : {
107 : return std::forward<T>(t).operator co_await();
108 : }
109 : else if constexpr (requires { operator co_await(std::forward<T>(t)); })
110 : {
111 : return operator co_await(std::forward<T>(t));
112 : }
113 : else
114 : {
115 0 : return std::forward<T>(t);
116 : }
117 : }
118 :
119 : template<typename A>
120 : using awaitable_return_t = decltype(
121 : get_awaiter(std::declval<A>()).await_resume()
122 : );
123 :
124 : } // namespace detail
125 :
126 : /** Concept for types that decompose to a specific typelist.
127 :
128 : A type satisfies `decomposes_to` if it can be decomposed via
129 : structured bindings into the specified types. This includes
130 : aggregates with matching member types and tuple-like types
131 : with matching element types.
132 :
133 : @tparam T The type to decompose.
134 : @tparam Types The expected element types after decomposition.
135 :
136 : @par Example
137 : @code
138 : struct result { int a; double b; };
139 :
140 : static_assert(decomposes_to<result, int, double>);
141 : static_assert(decomposes_to<std::tuple<int, double>, int, double>);
142 : @endcode
143 : */
144 : template <typename T, typename... Types>
145 : concept decomposes_to = requires(T&& t) {
146 : { detail::decomposed_types(std::forward<T>(t)) } -> std::same_as<std::tuple<Types...>>;
147 : };
148 :
149 : /** Concept for awaitables whose return type decomposes to a specific typelist.
150 :
151 : A type satisfies `awaitable_decomposes_to` if it is an awaitable
152 : (has `await_resume`) and its return type decomposes to the
153 : specified typelist.
154 :
155 : @tparam A The awaitable type.
156 : @tparam Types The expected element types after decomposition.
157 :
158 : @par Requirements
159 : @li `A` must be an awaitable (directly or via `operator co_await`)
160 : @li The return type of `await_resume()` must decompose to `Types...`
161 :
162 : @par Example
163 : @code
164 : // Constrain a function to accept only awaitables that return
165 : // a decomposable result of (error_code, size_t)
166 : template<typename A>
167 : requires awaitable_decomposes_to<A, std::error_code, std::size_t>
168 : task<void> process(A&& op)
169 : {
170 : auto [ec, n] = co_await std::forward<A>(op);
171 : if (ec)
172 : co_return;
173 : // process n bytes...
174 : }
175 : @endcode
176 : */
177 : template<typename A, typename... Types>
178 : concept awaitable_decomposes_to = requires {
179 : typename detail::awaitable_return_t<A>;
180 : } && decomposes_to<detail::awaitable_return_t<A>, Types...>;
181 :
182 : } // namespace capy
183 : } // namespace boost
184 :
185 : #endif
|