include/boost/capy/concept/decomposes_to.hpp

100.0% Lines (4/0/4) 0.0% List of functions (0/1/3)
decomposes_to.hpp
f(x) Functions (3)
Line TLA Hits 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 3x auto get_awaiter(T&& t)
105 {
106 if constexpr (requires { std::forward<T>(t).operator co_await(); })
107 {
108 1x return std::forward<T>(t).operator co_await();
109 }
110 else if constexpr (requires { operator co_await(std::forward<T>(t)); })
111 {
112 1x return operator co_await(std::forward<T>(t));
113 }
114 else
115 {
116 1x 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
187