Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Add new entry in Release_Notes.
[simgrid.git] / include / xbt / functional.hpp
1 /* Copyright (c) 2015-2023. The SimGrid Team. All rights reserved.          */
2
3 /* This program is free software; you can redistribute it and/or modify it
4  * under the terms of the license (GNU LGPL) which comes with this package. */
5
6 #ifndef XBT_FUNCTIONAL_HPP
7 #define XBT_FUNCTIONAL_HPP
8
9 #include <xbt/sysdep.h>
10
11 #include <cstddef>
12 #include <cstdlib>
13 #include <cstring>
14
15 #include <algorithm>
16 #include <array>
17 #include <exception>
18 #include <functional>
19 #include <memory>
20 #include <string>
21 #include <tuple>
22 #include <type_traits>
23 #include <utility>
24 #include <vector>
25
26 namespace simgrid::xbt {
27
28 template <class F> class MainFunction {
29   F code_;
30   std::shared_ptr<const std::vector<std::string>> args_;
31
32 public:
33   MainFunction(F code, std::vector<std::string>&& args)
34       : code_(std::move(code)), args_(std::make_shared<const std::vector<std::string>>(std::move(args)))
35   {
36   }
37   void operator()() const
38   {
39     std::vector<std::string> args = *args_;
40     std::vector<char*> argv(args.size() + 1); // argv[argc] is nullptr
41     std::transform(begin(args), end(args), begin(argv), [](std::string& s) { return &s.front(); });
42     code_(static_cast<int>(args.size()), argv.data());
43   }
44 };
45
46 template <class F> inline std::function<void()> wrap_main(F code, std::vector<std::string>&& args)
47 {
48   return MainFunction<F>(std::move(code), std::move(args));
49 }
50
51 template <class F> inline std::function<void()> wrap_main(F code, int argc, const char* const argv[])
52 {
53   std::vector<std::string> args(argv, argv + argc);
54   return MainFunction<F>(std::move(code), std::move(args));
55 }
56
57 namespace bits {
58 template <class F, class Tuple, std::size_t... I>
59 constexpr auto apply(F&& f, Tuple&& t, std::index_sequence<I...>)
60     -> decltype(std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...))
61 {
62   return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
63 }
64 }
65
66 /** Call a functional object with the values in the given tuple (from C++17)
67  *
68  *  @code{.cpp}
69  *  int foo(int a, bool b);
70  *
71  *  auto args = std::make_tuple(1, false);
72  *  int res = apply(foo, args);
73  *  @endcode
74  **/
75 template <class F, class Tuple>
76 constexpr auto apply(F&& f, Tuple&& t)
77     -> decltype(simgrid::xbt::bits::apply(std::forward<F>(f), std::forward<Tuple>(t),
78                                           std::make_index_sequence<std::tuple_size_v<typename std::decay_t<Tuple>>>()))
79 {
80   return simgrid::xbt::bits::apply(std::forward<F>(f), std::forward<Tuple>(t),
81                                    std::make_index_sequence<std::tuple_size_v<typename std::decay_t<Tuple>>>());
82 }
83
84 template<class T> class Task;
85
86 /** Type-erased run-once task
87  *
88  *  * Like std::function but callable only once.
89  *    However, it works with move-only types.
90  *
91  *  * Like std::packaged_task<> but without the shared state.
92  */
93 template<class R, class... Args>
94 class Task<R(Args...)> {
95   // Placeholder for some class type:
96   struct whatever {};
97
98   // Union used for storage:
99   using TaskUnion =
100       typename std::aligned_union_t<0, void*, std::pair<void (*)(), void*>, std::pair<void (whatever::*)(), whatever*>>;
101
102   // Is F suitable for small buffer optimization?
103   template<class F>
104   static constexpr bool canSBO()
105   {
106     return sizeof(F) <= sizeof(TaskUnion) &&
107       alignof(F) <= alignof(TaskUnion);
108   }
109
110   static_assert(canSBO<std::reference_wrapper<whatever>>(),
111     "SBO not working for reference_wrapper");
112
113   // Call (and possibly destroy) the function:
114   using call_function = R (*)(TaskUnion&, Args...);
115   // Destroy the function (of needed):
116   using destroy_function = void (*)(TaskUnion&);
117   // Move the function (otherwise memcpy):
118   using move_function = void (*)(TaskUnion& dest, TaskUnion& src);
119
120   // Vtable of functions for manipulating whatever is in the TaskUnion:
121   struct TaskVtable {
122     call_function call;
123     destroy_function destroy;
124     move_function move;
125   };
126
127   TaskUnion buffer_         = {};
128   const TaskVtable* vtable_ = nullptr;
129
130   void clear()
131   {
132     if (vtable_ && vtable_->destroy)
133       vtable_->destroy(buffer_);
134   }
135
136 public:
137   Task() = default;
138   explicit Task(std::nullptr_t) { /* Nothing to do */}
139   ~Task()
140   {
141     this->clear();
142   }
143
144   Task(Task const&) = delete;
145
146   Task(Task&& that) noexcept
147   {
148     if (that.vtable_ && that.vtable_->move)
149       that.vtable_->move(buffer_, that.buffer_);
150     else
151       std::memcpy(&buffer_, &that.buffer_, sizeof(buffer_));
152     vtable_      = std::move(that.vtable_);
153     that.vtable_ = nullptr;
154   }
155   Task& operator=(Task const& that) = delete;
156   Task& operator=(Task&& that) noexcept
157   {
158     this->clear();
159     if (that.vtable_ && that.vtable_->move)
160       that.vtable_->move(buffer_, that.buffer_);
161     else
162       std::memcpy(&buffer_, &that.buffer_, sizeof(buffer_));
163     vtable_      = std::move(that.vtable_);
164     that.vtable_ = nullptr;
165     return *this;
166   }
167
168 private:
169   template <class F> typename std::enable_if_t<canSBO<F>()> init(F task_code)
170   {
171     const static TaskVtable vtable {
172       // Call:
173       [](TaskUnion& buffer, Args... args) {
174         auto* src = reinterpret_cast<F*>(&buffer);
175         F code = std::move(*src);
176         src->~F();
177         // NOTE: std::forward<Args>(args)... is correct.
178         return code(std::forward<Args>(args)...);
179       },
180       // Destroy:
181       std::is_trivially_destructible_v<F> ?
182       static_cast<destroy_function>(nullptr) :
183       [](TaskUnion& buffer) {
184         auto* code = reinterpret_cast<F*>(&buffer);
185         code->~F();
186       },
187       // Move:
188       [](TaskUnion& dst, TaskUnion& src) {
189         auto* src_code = reinterpret_cast<F*>(&src);
190         auto* dst_code = reinterpret_cast<F*>(&dst);
191         new(dst_code) F(std::move(*src_code));
192         src_code->~F();
193       }
194     };
195     new (&buffer_) F(std::move(task_code));
196     vtable_ = &vtable;
197   }
198
199   template <class F> typename std::enable_if_t<not canSBO<F>()> init(F task_code)
200   {
201     const static TaskVtable vtable {
202       // Call:
203       [](TaskUnion& buffer, Args... args) {
204         // Delete F when we go out of scope:
205         std::unique_ptr<F> code(*reinterpret_cast<F**>(&buffer));
206         // NOTE: std::forward<Args>(args)... is correct.
207         return (*code)(std::forward<Args>(args)...);
208       },
209       // Destroy:
210       [](TaskUnion& buffer) {
211         F* code = *reinterpret_cast<F**>(&buffer);
212         delete code;
213       },
214       // Move:
215       nullptr
216     };
217     *reinterpret_cast<F**>(&buffer_) = new F(std::move(task_code));
218     vtable_ = &vtable;
219   }
220
221 public:
222   template <class F> explicit Task(F code) { this->init(std::move(code)); }
223
224   operator bool() const { return vtable_ != nullptr; }
225   bool operator!() const { return vtable_ == nullptr; }
226
227   R operator()(Args... args)
228   {
229     if (vtable_ == nullptr)
230       throw std::bad_function_call();
231     const TaskVtable* vtable = vtable_;
232     vtable_ = nullptr;
233     // NOTE: std::forward<Args>(args)... is correct.
234     // see C++ [func.wrap.func.inv] for an example
235     return vtable->call(buffer_, std::forward<Args>(args)...);
236   }
237 };
238
239 template<class F, class... Args>
240 class TaskImpl {
241   F code_;
242   std::tuple<Args...> args_;
243   using result_type = decltype(simgrid::xbt::apply(std::move(code_), std::move(args_)));
244
245 public:
246   TaskImpl(F code, std::tuple<Args...> args) :
247     code_(std::move(code)),
248     args_(std::move(args))
249   {}
250   result_type operator()()
251   {
252     return simgrid::xbt::apply(std::move(code_), std::move(args_));
253   }
254 };
255
256 template <class F, class... Args> auto make_task(F code, Args... args) -> Task<decltype(code(std::move(args)...))()>
257 {
258   TaskImpl<F, Args...> task(std::move(code), std::make_tuple(std::move(args)...));
259   return Task<decltype(code(std::move(args)...))()>(std::move(task));
260 }
261
262 } // namespace simgrid::xbt
263 #endif