1+ #pragma once
2+
3+ #include < functional>
4+ #include < print>
5+ #include < tuple>
6+ #include < type_traits>
7+ #include < utility>
8+
9+ namespace core ::meta::utils {
10+
11+ /* *
12+ * @class curried
13+ * @brief A class template that implements function currying - transforming a
14+ * function taking multiple arguments into a sequence of functions each taking a
15+ * single argument.
16+ *
17+ * @tparam F The type of the callable object to be curried
18+ * @tparam BoundArgs The types of the arguments already bound to the function
19+ *
20+ * This class stores a function and already bound arguments, and provides
21+ * operator() to either:
22+ * 1. Invoke the function if enough arguments are provided
23+ * 2. Return a new curried object with additional arguments bound
24+ */
25+ template <typename F, typename ... BoundArgs>
26+ class curried {
27+ F func; // /< The callable object to be curried
28+ std::tuple<BoundArgs...> bound; // /< Tuple of already bound arguments
29+
30+ /* *
31+ * @brief Helper function to invoke the stored function with bound and new
32+ * arguments
33+ *
34+ * @tparam Args Types of additional arguments
35+ * @tparam Is Index sequence for unpacking the bound arguments tuple
36+ * @param f The callable object (forwarded)
37+ * @param tup Tuple of bound arguments
38+ * @param seq Index sequence for tuple unpacking
39+ * @param args Additional arguments to pass to the function
40+ * @return Result of invoking the function with all arguments
41+ *
42+ * @note This is noexcept when the function invocation is noexcept
43+ */
44+ template <typename ... Args, std::size_t ... Is>
45+ static constexpr decltype (auto ) invoke_impl(
46+ F&& f, std::tuple<BoundArgs...>& tup, std::index_sequence<Is...>,
47+ Args&&... args) noexcept (std::is_nothrow_invocable_v<F, BoundArgs...,
48+ Args...>) {
49+ return std::invoke (std::forward<F>(f), std::get<Is>(tup)...,
50+ std::forward<Args>(args)...);
51+ }
52+
53+ public:
54+ /* *
55+ * @brief Construct a new curried object
56+ *
57+ * @param f The callable object to curry
58+ * @param args Initial arguments to bind
59+ *
60+ * @note Constructor is noexcept if both F and all BoundArgs are nothrow
61+ * move-constructible
62+ */
63+ constexpr explicit curried (F f, BoundArgs... args) noexcept (
64+ std::is_nothrow_move_constructible_v<F> &&
65+ (std::is_nothrow_move_constructible_v<BoundArgs> && ...))
66+ : func(std::move(f)), bound(std::make_tuple(std::move(args)...)) {}
67+
68+ /* *
69+ * @brief Const lvalue overload of function call operator for complete
70+ * invocation
71+ *
72+ * @tparam Args Types of additional arguments
73+ * @param args Additional arguments to pass to the function
74+ * @return Result of function invocation
75+ *
76+ * @note This overload is only available when the function can be invoked with
77+ * the current bound arguments and provided arguments
78+ */
79+ template <typename ... Args>
80+ constexpr decltype (auto ) operator()(Args&&... args) const &
81+ requires std::is_invocable_v<const F&, const BoundArgs&..., Args...>
82+ {
83+ return invoke_impl (func, const_cast <std::tuple<BoundArgs...>&>(bound),
84+ std::index_sequence_for<BoundArgs...>{},
85+ std::forward<Args>(args)...);
86+ }
87+
88+ /* *
89+ * @brief Rvalue overload of function call operator for complete invocation
90+ *
91+ * @tparam Args Types of additional arguments
92+ * @param args Additional arguments to pass to the function
93+ * @return Result of function invocation
94+ *
95+ * @note This overload is only available when the function can be invoked with
96+ * the current bound arguments and provided arguments
97+ */
98+ template <typename ... Args>
99+ constexpr decltype (auto ) operator()(Args&&... args) &&
100+ requires std::is_invocable_v<F, BoundArgs..., Args...>
101+ {
102+ return invoke_impl (std::move (func), bound,
103+ std::index_sequence_for<BoundArgs...>{},
104+ std::forward<Args>(args)...);
105+ }
106+
107+ /* *
108+ * @brief Const lvalue overload of function call operator for partial
109+ * application
110+ *
111+ * @tparam Args Types of additional arguments
112+ * @param args Additional arguments to bind
113+ * @return A new curried object with additional arguments bound
114+ *
115+ * @note This overload is only available when the function cannot yet be
116+ * invoked with the current bound arguments and provided arguments
117+ */
118+ template <typename ... Args>
119+ constexpr auto operator ()(Args&&... args) const &
120+ requires(!std::is_invocable_v<const F&, const BoundArgs&..., Args...>)
121+ {
122+ return curried<F, BoundArgs..., std::decay_t <Args>...>(
123+ func, std::get<BoundArgs>(bound)..., std::forward<Args>(args)...);
124+ }
125+
126+ /* *
127+ * @brief Rvalue overload of function call operator for partial application
128+ *
129+ * @tparam Args Types of additional arguments
130+ * @param args Additional arguments to bind
131+ * @return A new curried object with additional arguments bound
132+ *
133+ * @note This overload is only available when the function cannot yet be
134+ * invoked with the current bound arguments and provided arguments
135+ */
136+ template <typename ... Args>
137+ constexpr auto operator ()(Args&&... args) &&
138+ requires(!std::is_invocable_v<F, BoundArgs..., Args...>) {
139+ return curried<F, BoundArgs..., std::decay_t <Args>...>(
140+ std::move (func), std::move (std::get<BoundArgs>(bound))...,
141+ std::forward<Args>(args)...);
142+ }
143+
144+ /* *
145+ * @brief Const lvalue overload of zero-argument function call operator
146+ * @return Result of function invocation with just the bound arguments
147+ *
148+ * @note This overload is only available when the function can be invoked
149+ * with just the bound arguments
150+ */
151+ constexpr decltype (auto ) operator()() const &
152+ requires std::is_invocable_v<const F&, const BoundArgs&...>
153+ {
154+ return invoke_impl (func, const_cast <std::tuple<BoundArgs...>&>(bound),
155+ std::index_sequence_for<BoundArgs...>{});
156+ }
157+
158+ /* *
159+ * @brief Rvalue overload of zero-argument function call operator
160+ * @return Result of function invocation with just the bound arguments
161+ *
162+ * @note This overload is only available when the function can be invoked with
163+ * just the bound arguments
164+ */
165+ constexpr decltype (auto ) operator()() &&
166+ requires std::is_invocable_v<F, BoundArgs...>
167+ {
168+ return invoke_impl (std::move (func), bound,
169+ std::index_sequence_for<BoundArgs...>{});
170+ }
171+ };
172+
173+ /* *
174+ * @brief Creates a curried function object with no initially bound arguments
175+ *
176+ * @tparam F Type of the callable object
177+ * @param f The callable object to curry
178+ * @return A curried wrapper around f
179+ *
180+ * @note This is noexcept if constructing the curried wrapper is noexcept
181+ */
182+ template <typename F>
183+ constexpr auto curry (F&& f) noexcept (
184+ std::is_nothrow_constructible_v<std::decay_t <F>, F>) {
185+ return curried<std::decay_t <F>>(std::forward<F>(f));
186+ }
187+
188+ /* *
189+ * @brief Creates a curried function object with initially bound arguments
190+ *
191+ * @tparam F Type of the callable object
192+ * @tparam Args Types of the arguments to bind initially
193+ * @param f The callable object to curry
194+ * @param args Arguments to bind initially
195+ * @return A curried wrapper around f with args bound
196+ *
197+ * @note This is noexcept if constructing the curried wrapper with arguments is
198+ * noexcept
199+ * @note The returned object is marked [[nodiscard]] to prevent accidental
200+ * discarding
201+ */
202+ [[nodiscard]] template <typename F, typename ... Args>
203+ constexpr auto curry (F&& f, Args&&... args) noexcept (
204+ noexcept (curried<std::decay_t <F>, std::decay_t <Args>...>(
205+ std::forward<F>(f), std::forward<Args>(args)...))) {
206+ return curried<std::decay_t <F>, std::decay_t <Args>...>(
207+ std::forward<F>(f), std::forward<Args>(args)...);
208+ }
209+
210+ } // namespace core::meta::utils
0 commit comments