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