cpp-toolbox  0.0.1
A toolbox library for C++
Loading...
Searching...
No Matches
functional_impl.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cpp-toolbox/functional/functional.hpp> // Include the main header
4
5// Necessary includes for implementation
6#include <functional> // For std::function in memoize
7#include <map>
8#include <memory> // For std::shared_ptr in PIMPL
9#include <mutex>
10#include <numeric> // For std::accumulate
11#include <optional>
12#include <stdexcept> // For std::logic_error, std::invalid_argument
13#include <tuple> // For std::tuple used in MemoizedFunction and zip
14#include <type_traits> // For various type traits
15#include <unordered_map> // For zip_to_unordered_map
16#include <utility> // For std::forward, std::move
17#include <variant>
18#include <vector> // For map/filter container results
19
20namespace toolbox::functional
21{
22
23// --- Implementation detail for memoize_explicit ---
24namespace detail_impl
25{
26
27template<typename R, typename... Args>
29{
30 using FuncType = std::function<R(Args...)>;
31 using KeyType = std::tuple<std::decay_t<Args>...>;
32 using ResultType = R;
33
35 std::map<KeyType, ResultType> cache_;
36 std::mutex cache_mutex_;
37
39 : original_func_(std::move(f))
40 {
41 }
42};
43
44} // namespace detail_impl
45
46// --- Implementation for MemoizedFunction<R(Args...)> using PIMPL ---
47
48template<typename R, typename... Args>
49struct MemoizedFunction<R(Args...)>::State
50{
51 using FuncType = std::function<R(Args...)>;
52 using KeyType = std::tuple<std::decay_t<Args>...>;
53 using ResultType = R;
54
56 std::map<KeyType, ResultType> cache_;
57 std::mutex cache_mutex_;
58
59 explicit State(FuncType f)
60 : original_func_(std::move(f))
61 {
62 }
63};
64
65template<typename R, typename... Args>
67 : state_(std::make_shared<State>(std::move(f)))
68{
69}
70
71template<typename R, typename... Args>
72auto MemoizedFunction<R(Args...)>::operator()(Args... args) -> ResultType
73{
74 // Use state_ pointer to access members
75 KeyType key = std::make_tuple(std::decay_t<Args>(args)...);
76
77 {
78 std::lock_guard<std::mutex> lock(state_->cache_mutex_);
79 auto it = state_->cache_.find(key);
80 if (it != state_->cache_.end()) {
81 return it->second;
82 }
83 }
84
85 // Call the original function through the state object
86 ResultType result = state_->original_func_(std::forward<Args>(args)...);
87
88 {
89 std::lock_guard<std::mutex> lock(state_->cache_mutex_);
90 // Double-check insertion to handle race condition where another thread
91 // calculated it
92 auto it = state_->cache_.find(key);
93 if (it == state_->cache_.end()) {
94 state_->cache_.emplace(std::move(key), result);
95 return result;
96 } else {
97 // Another thread finished first, return its result
98 return it->second;
99 }
100 }
101}
102
103// --- Implementations for other functions ---
104
105template<typename G, typename F>
106auto compose(G&& g, F&& f)
107{
108 return
109 [g = std::forward<G>(g), f = std::forward<F>(f)](auto&&... args) mutable
110 -> decltype(g(f(std::forward<decltype(args)>(args)...)))
111 { return g(f(std::forward<decltype(args)>(args)...)); };
112}
113
114template<typename F1, typename... FRest>
115auto compose(F1&& f1, FRest&&... rest)
116{
117 if constexpr (sizeof...(FRest) == 0) {
118 // Base case: return the single function wrapped in a lambda
119 return [f1_cap =
120 std::forward<F1>(f1)](auto&&... args) mutable -> decltype(auto)
121 { return f1_cap(std::forward<decltype(args)>(args)...); };
122 } else {
123 // Recursive step: compose the rest of the functions first
124 auto composed_rest = compose(std::forward<FRest>(rest)...);
125 // Then compose f1 with the result
126 return [f1_cap = std::forward<F1>(f1),
127 composed_rest_cap = std::move(composed_rest)](
128 auto&&... args) mutable -> decltype(auto)
129 {
130 // Apply composed_rest first, then f1_cap
131 return f1_cap(composed_rest_cap(std::forward<decltype(args)>(args)...));
132 };
133 }
134}
135
136inline auto compose()
137{
138 throw std::logic_error("compose called with no functions");
139}
140
141template<typename F, typename Arg1>
142auto bind_first(F&& f, Arg1&& arg1)
143{
144 return [f = std::forward<F>(f),
145 arg1 = std::forward<Arg1>(arg1)](
146 auto&&... rest_args) mutable // Ensure mutable if f or arg1 state
147 // needs change
148 -> decltype(f(arg1, std::forward<decltype(rest_args)>(rest_args)...))
149 { return f(arg1, std::forward<decltype(rest_args)>(rest_args)...); };
150}
151
152template<typename T, typename F>
153auto map(const std::optional<T>& opt, F&& f)
154 -> std::optional<std::invoke_result_t<F, const T&>>
155{
156 using result_type = std::invoke_result_t<F, const T&>;
157 using result_optional_type = std::optional<result_type>;
158
159 if (opt.has_value()) {
160 return result_optional_type(std::invoke(std::forward<F>(f), *opt));
161 } else {
162 return std::nullopt;
163 }
164}
165
166template<typename T, typename F>
167auto map(std::optional<T>&& opt, F&& f)
168 -> std::optional<std::invoke_result_t<F, T&&>>
169{
170 using result_type = std::invoke_result_t<F, T&&>;
171 using result_optional_type = std::optional<result_type>;
172
173 if (opt.has_value()) {
174 return result_optional_type(
175 std::invoke(std::forward<F>(f), std::move(*opt)));
176 } else {
177 return std::nullopt;
178 }
179}
180
181template<typename T, typename F>
182auto flatMap(const std::optional<T>& opt, F&& f)
183 -> std::invoke_result_t<F, const T&>
184{
185 using result_optional_type = std::invoke_result_t<F, const T&>;
186
187 static_assert(detail::is_optional_v<result_optional_type>,
188 "Function passed to flatMap must return a std::optional type.");
189
190 if (opt.has_value()) {
191 return std::invoke(std::forward<F>(f), *opt);
192 } else {
193 return result_optional_type {}; // Return default-constructed optional
194 // (nullopt)
195 }
196}
197
198template<typename T, typename F>
199auto flatMap(std::optional<T>&& opt, F&& f) -> std::invoke_result_t<F, T&&>
200{
201 using result_optional_type = std::invoke_result_t<F, T&&>;
202 static_assert(detail::is_optional_v<result_optional_type>,
203 "Function passed to flatMap must return a std::optional type.");
204
205 if (opt.has_value()) {
206 return std::invoke(std::forward<F>(f), std::move(*opt));
207 } else {
208 return result_optional_type {}; // Return default-constructed optional
209 // (nullopt)
210 }
211}
212
213template<typename T, typename U>
214auto orElse(const std::optional<T>& opt, U&& default_value) -> T
215{
216 static_assert(
217 std::is_convertible_v<U, T>,
218 "Default value type must be convertible to optional's value type T.");
219
220 return opt.value_or(std::forward<U>(default_value));
221}
222
223template<typename T, typename F>
224auto orElseGet(const std::optional<T>& opt, F&& default_func) -> T
225{
226 static_assert(std::is_invocable_v<F>,
227 "Default function must be callable with no arguments.");
228
229 using default_result_type = std::invoke_result_t<F>;
230 static_assert(std::is_convertible_v<default_result_type, T>,
231 "Default function's return type must be convertible to "
232 "optional's value type T.");
233
234 if (opt.has_value()) {
235 return *opt;
236 } else {
237 return static_cast<T>(std::invoke(std::forward<F>(default_func)));
238 }
239}
240
241template<typename T, typename P>
242auto filter(const std::optional<T>& opt, P&& p) -> std::optional<T>
243{
244#if __cpp_lib_is_invocable >= 201703L
245 static_assert(
246 std::is_invocable_r_v<bool, P, const T&>
247 || std::is_convertible_v<std::invoke_result_t<P, const T&>, bool>,
248 "Predicate must be callable with const T& and return bool or "
249 "bool-convertible.");
250#else
251 // Fallback check for older compilers
252 static_assert(
253 std::is_convertible_v<std::invoke_result_t<P, const T&>, bool>,
254 "Predicate must be callable with const T& and return bool-convertible.");
255#endif
256
257 return (opt.has_value() && std::invoke(std::forward<P>(p), *opt))
258 ? opt
259 : std::nullopt;
260}
261
262template<typename T, typename P>
263auto filter(std::optional<T>&& opt, P&& p) -> std::optional<T>
264{
265#if __cpp_lib_is_invocable >= 201703L
266 static_assert(
267 std::is_invocable_r_v<bool, P, const T&>
268 || std::is_convertible_v<std::invoke_result_t<P, const T&>, bool>,
269 "Predicate must be callable with const T& and return bool or "
270 "bool-convertible.");
271#else
272 // Fallback check for older compilers
273 static_assert(
274 std::is_convertible_v<std::invoke_result_t<P, const T&>, bool>,
275 "Predicate must be callable with const T& and return bool-convertible.");
276#endif
277
278 if (opt.has_value() && std::invoke(std::forward<P>(p), *opt)) {
279 return std::move(opt);
280 } else {
281 return std::nullopt;
282 }
283}
284
285template<typename... Ts, typename... Fs>
286auto match(const std::variant<Ts...>& var, Fs&&... visitors) -> decltype(auto)
287{
288 static_assert(
289 sizeof...(Ts) == sizeof...(Fs),
290 "Number of visitors must match the number of types in the variant");
291 auto visitor_set = detail::overloaded {std::forward<Fs>(visitors)...};
292 return std::visit(visitor_set, var);
293}
294
295template<typename... Ts, typename... Fs>
296auto match(std::variant<Ts...>& var, Fs&&... visitors) -> decltype(auto)
297{
298 static_assert(
299 sizeof...(Ts) == sizeof...(Fs),
300 "Number of visitors must match the number of types in the variant");
301 auto visitor_set = detail::overloaded {std::forward<Fs>(visitors)...};
302 return std::visit(visitor_set, var);
303}
304
305template<typename... Ts, typename... Fs>
306auto match(std::variant<Ts...>&& var, Fs&&... visitors) -> decltype(auto)
307{
308 static_assert(
309 sizeof...(Ts) == sizeof...(Fs),
310 "Number of visitors must match the number of types in the variant");
311 auto visitor_set = detail::overloaded {std::forward<Fs>(visitors)...};
312 return std::visit(visitor_set, std::move(var));
313}
314
315template<typename ResultVariant, typename... Ts, typename F>
316auto map(const std::variant<Ts...>& var, F&& f) -> ResultVariant
317{
318 return std::visit(
319 [&f](const auto& value) -> ResultVariant
320 {
321 if constexpr (std::is_void_v<
322 std::invoke_result_t<F, decltype(value)>>) {
323 // This path should ideally not be taken if ResultVariant is used
324 // correctly
325 std::invoke(std::forward<F>(f), value);
326 // Cannot construct ResultVariant from void. Check if ResultVariant
327 // has monostate? This static_assert might be too strict if monostate
328 // is intended.
329 static_assert(
330 !std::is_void_v<std::invoke_result_t<F, decltype(value)>>,
331 "Mapping function returning void requires ResultVariant to "
332 "support default construction (e.g., std::monostate).");
333 return ResultVariant {}; // Or throw, depending on desired semantics
334 } else {
335 return ResultVariant {std::invoke(std::forward<F>(f), value)};
336 }
337 },
338 var);
339}
340
341template<typename ResultVariant, typename... Ts, typename F>
342auto map(std::variant<Ts...>& var, F&& f) -> ResultVariant
343{
344 return std::visit(
345 [&f](auto& value) -> ResultVariant
346 {
347 if constexpr (std::is_void_v<
348 std::invoke_result_t<F, decltype(value)>>) {
349 std::invoke(std::forward<F>(f), value);
350 static_assert(
351 !std::is_void_v<std::invoke_result_t<F, decltype(value)>>,
352 "Mapping function returning void requires ResultVariant to "
353 "support default construction (e.g., std::monostate).");
354 return ResultVariant {};
355 } else {
356 return ResultVariant {std::invoke(std::forward<F>(f), value)};
357 }
358 },
359 var);
360}
361
362template<typename ResultVariant, typename... Ts, typename F>
363auto map(std::variant<Ts...>&& var, F&& f) -> ResultVariant
364{
365 return std::visit(
366 [&f](auto&& value) -> ResultVariant
367 {
368 if constexpr (std::is_void_v<std::invoke_result_t<
369 F,
370 decltype(std::forward<decltype(value)>(value))>>)
371 {
372 std::invoke(std::forward<F>(f), std::forward<decltype(value)>(value));
373 static_assert(
374 !std::is_void_v<std::invoke_result_t<
375 F,
376 decltype(std::forward<decltype(value)>(value))>>,
377 "Mapping function returning void requires ResultVariant to "
378 "support default construction (e.g., std::monostate).");
379 return ResultVariant {};
380 } else {
381 return ResultVariant {std::invoke(
382 std::forward<F>(f), std::forward<decltype(value)>(value))};
383 }
384 },
385 std::move(var));
386}
387
388template<typename Container, typename Func>
389auto map(const Container& input, Func&& f) -> std::vector<
390 std::invoke_result_t<Func, typename Container::const_reference>>
391{
392 using ResultValueType =
393 std::invoke_result_t<Func, typename Container::const_reference>;
394 std::vector<ResultValueType> result;
395
397 result.reserve(input.size());
398 }
399
400 std::transform(std::cbegin(input), // Use std::cbegin for const-correctness
401 std::cend(input), // Use std::cend for const-correctness
402 std::back_inserter(result),
403 std::forward<Func>(f));
404
405 return result;
406}
407
408template<typename Container, typename Predicate>
409auto filter(const Container& input, Predicate&& p)
410 -> std::vector<typename Container::value_type>
411{
412 using ValueType = typename Container::value_type;
413 std::vector<ValueType> result;
414
415 std::copy_if(std::cbegin(input), // Use std::cbegin
416 std::cend(input), // Use std::cend
417 std::back_inserter(result),
418 std::forward<Predicate>(p));
419
420 return result;
421}
422
423template<typename Container, typename T, typename BinaryOp>
424auto reduce(const Container& input, T identity, BinaryOp&& op) -> T
425{
426 return std::accumulate(std::cbegin(input), // Use std::cbegin
427 std::cend(input), // Use std::cend
428 std::move(identity),
429 std::forward<BinaryOp>(op));
430}
431
432template<typename Container, typename BinaryOp>
433auto reduce(const Container& input, BinaryOp&& op) ->
434 typename Container::value_type
435{
436 if (std::empty(input)) { // Use std::empty for check
437 throw std::invalid_argument(
438 "reduce called on empty container without an identity value");
439 }
440
441 auto it = std::cbegin(input); // Use std::cbegin
442 typename Container::value_type result = *it;
443 ++it;
444
445 return std::accumulate(it,
446 std::cend(input), // Use std::cend
447 std::move(result), // Move the initial value
448 std::forward<BinaryOp>(op));
449}
450
451template<typename... Containers>
452auto zip(const Containers&... containers) -> std::vector<
453 std::tuple<decltype(*std::cbegin(std::declval<const Containers&>()))...>>
454{
455 using ResultTupleType =
456 std::tuple<decltype(*std::cbegin(std::declval<const Containers&>()))...>;
457 std::vector<ResultTupleType> result;
458
459 if constexpr (sizeof...(containers) == 0) {
460 return result;
461 }
462
463 const size_t min_size = detail::get_min_size(containers...);
464 if (min_size == 0) {
465 return result;
466 }
467
468 result.reserve(min_size);
469
470 auto iter_tuple = std::make_tuple(std::cbegin(containers)...);
471 auto index_seq = std::index_sequence_for<Containers...> {};
472
473 for (size_t i = 0; i < min_size; ++i) {
474 // Use std::apply to unpack the tuple for emplace_back
475 std::apply([&result](auto&&... its) { result.emplace_back(*its...); },
476 iter_tuple);
477 detail::increment_iterators(iter_tuple, index_seq);
478 }
479
480 return result;
481}
482
483template<typename ContainerKeys,
484 typename ContainerValues,
485 typename Key,
486 typename Value,
487 typename Hash,
488 typename KeyEqual,
489 typename Alloc>
490auto zip_to_unordered_map(const ContainerKeys& keys,
491 const ContainerValues& values)
492 -> std::unordered_map<Key, Value, Hash, KeyEqual, Alloc>
493{
494 using ResultMapType = std::unordered_map<Key, Value, Hash, KeyEqual, Alloc>;
495 ResultMapType result;
496
497 auto keys_it = std::cbegin(keys);
498 auto keys_end = std::cend(keys);
499 auto values_it = std::cbegin(values);
500 auto values_end = std::cend(values);
501
502 // Optionally reserve based on min size if possible
505 {
506 result.reserve(std::min(keys.size(), values.size()));
507 }
508
509 while (keys_it != keys_end && values_it != values_end) {
510 // Use try_emplace to avoid overwriting existing keys if that's desired,
511 // or use emplace/insert depending on exact requirements for duplicates.
512 result.emplace(*keys_it, *values_it);
513 ++keys_it;
514 ++values_it;
515 }
516
517 return result;
518}
519
520template<typename Signature, typename Func>
521auto memoize(Func&& f)
522{
523 std::function<Signature> func_wrapper = std::forward<Func>(f);
524 // Return the MemoizedFunction wrapper directly
525 return MemoizedFunction<Signature>(std::move(func_wrapper));
526}
527
528// --- Implementation for memoize_explicit ---
529template<typename R, typename... Args, typename Func>
530auto memoize_explicit(Func&& f) -> std::function<R(Args...)>
531{
532 // Use the independent helper state struct defined above
533 using HelperStateType = detail_impl::MemoizeHelperState<R, Args...>;
534 auto state = std::make_shared<HelperStateType>(
535 std::function<R(Args...)>(std::forward<Func>(f)));
536
537 // The returned lambda captures the shared_ptr to the helper state.
538 // This lambda is copy-constructible.
539 return [state](Args... args) -> R
540 {
541 using KeyType = typename HelperStateType::KeyType;
542 KeyType key = std::make_tuple(std::decay_t<Args>(args)...);
543
544 {
545 std::lock_guard<std::mutex> lock(state->cache_mutex_);
546 auto it = state->cache_.find(key);
547 if (it != state->cache_.end()) {
548 return it->second;
549 }
550 }
551
552 // Call the original function stored in the helper state
553 R result = state->original_func_(std::forward<Args>(args)...);
554
555 {
556 std::lock_guard<std::mutex> lock(state->cache_mutex_);
557 // Double-check insertion in the helper state's cache
558 auto it = state->cache_.find(key);
559 if (it == state->cache_.end()) {
560 state->cache_.emplace(std::move(key), result);
561 return result;
562 } else {
563 return it->second;
564 }
565 }
566 };
567}
568
569} // namespace toolbox::functional
auto get_min_size(const Containers &... containers) -> size_t
获取多个容器中的最小大小的辅助函数 / Helper function to get minimum size from multiple containers
Definition functional.hpp:151
void increment_iterators(Tuple &iter_tuple, std::index_sequence< Is... >)
增加元组中所有迭代器的辅助函数 / Helper function to increment all iterators in a tuple
Definition functional.hpp:171
Definition functional.hpp:66
CPP_TOOLBOX_EXPORT auto reduce(const Container &input, T identity, BinaryOp &&op) -> T
使用带初始值的二元操作归约容器元素 / Reduce container elements using a binary operation with initial value
Definition functional_impl.hpp:424
CPP_TOOLBOX_EXPORT auto orElseGet(const std::optional< T > &opt, F &&default_func) -> T
返回包含的值或调用函数获取默认值 / Returns the contained value or calls function for default
Definition functional_impl.hpp:224
CPP_TOOLBOX_EXPORT auto filter(const std::optional< T > &opt, P &&p) -> std::optional< T >
基于谓词过滤optional / Filters an optional based on a predicate
Definition functional_impl.hpp:242
CPP_TOOLBOX_EXPORT auto zip_to_unordered_map(const ContainerKeys &keys, const ContainerValues &values) -> std::unordered_map< Key, Value, Hash, KeyEqual, Alloc >
将两个序列压缩成无序映射 / Zip two sequences into an unordered_map
Definition functional_impl.hpp:490
CPP_TOOLBOX_EXPORT auto orElse(const std::optional< T > &opt, U &&default_value) -> T
返回包含的值或默认值 / Returns the contained value or a default
Definition functional_impl.hpp:214
CPP_TOOLBOX_EXPORT auto match(const std::variant< Ts... > &var, Fs &&... visitors) -> decltype(auto)
使用访问器函数对variant进行模式匹配 / Pattern matches on a variant using visitor functions
Definition functional_impl.hpp:286
CPP_TOOLBOX_EXPORT auto compose()
空的compose函数,会抛出错误 / Empty compose function that throws an error
Definition functional_impl.hpp:136
CPP_TOOLBOX_EXPORT auto memoize(Func &&f)
创建带显式签名的记忆化函数 / Create a memoized function with explicit signature
Definition functional_impl.hpp:521
CPP_TOOLBOX_EXPORT auto map(const std::optional< T > &opt, F &&f) -> std::optional< std::invoke_result_t< F, const T & > >
在optional值上映射函数 / Maps a function over an optional value
Definition functional_impl.hpp:153
CPP_TOOLBOX_EXPORT auto flatMap(const std::optional< T > &opt, F &&f) -> std::invoke_result_t< F, const T & >
在optional值上平面映射函数 / Flat maps a function over an optional value
Definition functional_impl.hpp:182
CPP_TOOLBOX_EXPORT auto zip(const Containers &... containers) -> std::vector< std::tuple< decltype(*std::cbegin(std::declval< const Containers & >()))... > >
将多个容器压缩成元组向量 / Zip multiple containers into a vector of tuples
Definition functional_impl.hpp:452
CPP_TOOLBOX_EXPORT auto memoize_explicit(Func &&f) -> std::function< R(Args...)>
创建带显式返回和参数类型的记忆化函数 / Create a memoized function with explicit return and argument types
Definition functional_impl.hpp:530
class CPP_TOOLBOX_EXPORT MemoizedFunction
缓存函数结果的记忆化函数类 / Memoized function class that caches function results
Definition functional.hpp:820
CPP_TOOLBOX_EXPORT auto bind_first(F &&f, Arg1 &&arg1)
绑定函数的第一个参数 / Binds the first argument of a function
Definition functional_impl.hpp:142
std::mutex cache_mutex_
Definition functional_impl.hpp:57
std::map< KeyType, ResultType > cache_
Definition functional_impl.hpp:56
std::function< R(Args...)> FuncType
Definition functional_impl.hpp:51
State(FuncType f)
Definition functional_impl.hpp:59
std::tuple< std::decay_t< Args >... > KeyType
Definition functional_impl.hpp:52
FuncType original_func_
Definition functional_impl.hpp:55
检查类型是否有size()成员函数的类型特征 / Type trait to check if type has size() member function
Definition functional.hpp:128
用于创建重载集的辅助类 / Helper class for creating overload sets
Definition functional.hpp:84
MemoizeHelperState(FuncType f)
Definition functional_impl.hpp:38
FuncType original_func_
Definition functional_impl.hpp:34
R ResultType
Definition functional_impl.hpp:32
std::map< KeyType, ResultType > cache_
Definition functional_impl.hpp:35
std::function< R(Args...)> FuncType
Definition functional_impl.hpp:30
std::mutex cache_mutex_
Definition functional_impl.hpp:36
std::tuple< std::decay_t< Args >... > KeyType
Definition functional_impl.hpp:31