Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
49d33f4e1c9e7f0895d31d50b2ebed1b4125f192
[simgrid.git] / include / xbt / functional.hpp
1 /* Copyright (c) 2015-2016. The SimGrid Team.
2  * All rights reserved.                                                     */
3
4 /* This program is free software; you can redistribute it and/or modify it
5  * under the terms of the license (GNU LGPL) which comes with this package. */
6
7 #ifndef XBT_FUNCTIONAL_HPP
8 #define XBT_FUNCTIONAL_HPP
9
10 #include <cstddef>
11 #include <cstdlib>
12 #include <cstring>
13
14 #include <exception>
15 #include <functional>
16 #include <memory>
17 #include <string>
18 #include <tuple>
19 #include <type_traits>
20 #include <utility>
21 #include <vector>
22
23 #include <xbt/sysdep.h>
24 #include <xbt/utility.hpp>
25
26 namespace simgrid {
27 namespace xbt {
28
29 template<class F>
30 class MainFunction {
31 private:
32   F code_;
33   std::shared_ptr<const std::vector<std::string>> args_;
34 public:
35   MainFunction(F code, std::vector<std::string> args) :
36     code_(std::move(code)),
37     args_(std::make_shared<const std::vector<std::string>>(std::move(args)))
38   {}
39   int operator()() const
40   {
41     const int argc = args_->size();
42     std::vector<std::string> args = *args_;
43     std::unique_ptr<char*[]> argv(new char*[argc + 1]);
44     for (int i = 0; i != argc; ++i)
45       argv[i] = args[i].empty() ? const_cast<char*>(""): &args[i].front();
46     argv[argc] = nullptr;
47     return code_(argc, argv.get());
48   }
49 };
50
51 template<class F> inline
52 std::function<void()> wrapMain(F code, std::vector<std::string> args)
53 {
54   return MainFunction<F>(std::move(code), std::move(args));
55 }
56
57 template<class F> inline
58 std::function<void()> wrapMain(F code, int argc, const char*const argv[])
59 {
60   std::vector<std::string> args(argv, argv + argc);
61   return MainFunction<F>(std::move(code), std::move(args));
62 }
63
64 namespace bits {
65 template <class F, class Tuple, std::size_t... I>
66 constexpr auto apply(F&& f, Tuple&& t, simgrid::xbt::index_sequence<I...>)
67   -> decltype(std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...))
68 {
69   return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
70 }
71 }
72
73 /** Call a functional object with the values in the given tuple (from C++17)
74  *
75  *  @code{.cpp}
76  *  int foo(int a, bool b);
77  *
78  *  auto args = std::make_tuple(1, false);
79  *  int res = apply(foo, args);
80  *  @encode
81  **/
82 template <class F, class Tuple>
83 constexpr auto apply(F&& f, Tuple&& t)
84   -> decltype(simgrid::xbt::bits::apply(
85     std::forward<F>(f),
86     std::forward<Tuple>(t),
87     simgrid::xbt::make_index_sequence<
88       std::tuple_size<typename std::decay<Tuple>::type>::value
89     >()))
90 {
91   return simgrid::xbt::bits::apply(
92     std::forward<F>(f),
93     std::forward<Tuple>(t),
94     simgrid::xbt::make_index_sequence<
95       std::tuple_size<typename std::decay<Tuple>::type>::value
96     >());
97 }
98
99 template<class T> class Task;
100
101 namespace bits {
102
103   // Something similar exist in C++14:
104   template<class T>
105   constexpr T max(T a, T b)
106   {
107     return (a > b) ? a : b;
108   }
109   template<class T, class... Args>
110   constexpr T max(T a, Args... b)
111   {
112     return max(std::forward<T>(a), max(std::forward<Args>(b)...));
113   }
114
115   struct whatever {};
116
117   // What we can store in a Task:
118   typedef void* ptr_callback;
119   struct funcptr_callback {
120     // Placeholder for any function pointer:
121     void(*callback)();
122     void* data;
123   };
124   struct member_funcptr_callback {
125     // Placeholder for any pointer to member function:
126     void (whatever::* callback)();
127     whatever* data;
128   };
129   typedef char any_callback[max(
130     sizeof(ptr_callback),
131     sizeof(funcptr_callback),
132     sizeof(member_funcptr_callback)
133     )];
134
135   // Union of what we can store in a Task:
136   union TaskErasure {
137     ptr_callback ptr;
138     funcptr_callback funcptr;
139     member_funcptr_callback member_funcptr;
140     any_callback any;
141   };
142
143   // Can we copy F in Task (or do we have to use the heap)?
144   template<class F>
145   constexpr bool isUsableDirectlyInTask()
146   {
147     // The only types we can portably store directly in the Task are the
148     // trivially copyable ones (we can memcpy) which are small enough to fit:
149     return std::is_trivially_copyable<F>::value &&
150       sizeof(F) <= sizeof(bits::any_callback);
151   }
152
153 }
154
155 /** Type-erased run-once task
156  *
157  *  * Like std::function but callable only once.
158  *    However, it works with move-only types.
159  *
160  *  * Like std::packaged_task<> but without the shared state.
161  */
162 template<class R, class... Args>
163 class Task<R(Args...)> {
164 private:
165
166   typedef bits::TaskErasure TaskErasure;
167   struct TaskErasureVtable {
168     // Call (and possibly destroy) the function:
169     R (*call)(TaskErasure&, Args...);
170     // Destroy the function:
171     void (*destroy)(TaskErasure&);
172   };
173
174   TaskErasure code_;
175   const TaskErasureVtable* vtable_ = nullptr;
176
177 public:
178   Task() {}
179   Task(std::nullptr_t) {}
180   ~Task()
181   {
182     if (vtable_ && vtable_->destroy)
183       vtable_->destroy(code_);
184   }
185
186   Task(Task const&) = delete;
187   Task& operator=(Task const&) = delete;
188
189   Task(Task&& that)
190   {
191     std::memcpy(&code_, &that.code_, sizeof(code_));
192     vtable_ = that.vtable_;
193     that.vtable_ = nullptr;
194   }
195   Task& operator=(Task&& that)
196   {
197     if (vtable_ && vtable_->destroy)
198       vtable_->destroy(code_);
199     std::memcpy(&code_, &that.code_, sizeof(code_));
200     vtable_ = that.vtable_;
201     that.vtable_ = nullptr;
202     return *this;
203   }
204
205   template<class F,
206     typename = typename std::enable_if<bits::isUsableDirectlyInTask<F>()>::type>
207   Task(F const& code)
208   {
209     const static TaskErasureVtable vtable {
210       // Call:
211       [](TaskErasure& erasure, Args... args) -> R {
212         // We need to wrap F un a union because F might not have a default
213         // constructor: this is especially the case for lambdas.
214         union no_ctor {
215           no_ctor() {}
216           ~no_ctor() {}
217           F code ;
218         } code;
219         if (!std::is_empty<F>::value)
220           // AFAIU, this is safe as per [basic.types]:
221           std::memcpy(&code.code, &erasure.any, sizeof(code.code));
222         code.code(std::forward<Args>(args)...);
223       },
224       // Destroy:
225       nullptr
226     };
227     if (!std::is_empty<F>::value)
228       std::memcpy(&code_.any, &code, sizeof(code));
229     vtable_ = &vtable;
230   }
231
232   template<class F,
233     typename = typename std::enable_if<!bits::isUsableDirectlyInTask<F>()>::type>
234   Task(F code)
235   {
236     const static TaskErasureVtable vtable {
237       // Call:
238       [](TaskErasure& erasure, Args... args) -> R {
239         // Delete F when we go out of scope:
240         std::unique_ptr<F> code(static_cast<F*>(erasure.ptr));
241         (*code)(std::forward<Args>(args)...);
242       },
243       // Destroy:
244       [](TaskErasure& erasure) {
245         F* code = static_cast<F*>(erasure.ptr);
246         delete code;
247       }
248     };
249     code_.ptr = new F(std::move(code));
250     vtable_ = &vtable;
251   }
252
253   template<class F>
254   Task(std::reference_wrapper<F> code)
255   {
256     const static TaskErasureVtable vtable {
257       // Call:
258       [](TaskErasure& erasure, Args... args) -> R {
259         F* code = static_cast<F*>(erasure.ptr);
260         (*code)(std::forward<Args>(args)...);
261       },
262       // Destroy:
263       nullptr
264     };
265     code.code_.ptr = code.get();
266     vtable_ = &vtable;
267   }
268
269   operator bool() const { return vtable_ != nullptr; }
270   bool operator!() const { return vtable_ == nullptr; }
271
272   R operator()(Args... args)
273   {
274     if (!vtable_)
275       throw std::bad_function_call();
276     const TaskErasureVtable* vtable = vtable_;
277     vtable_ = nullptr;
278     return vtable->call(code_, std::forward<Args>(args)...);
279   }
280 };
281
282 template<class F, class... Args>
283 class TaskImpl {
284 private:
285   F code_;
286   std::tuple<Args...> args_;
287   typedef decltype(simgrid::xbt::apply(std::move(code_), std::move(args_))) result_type;
288 public:
289   TaskImpl(F code, std::tuple<Args...> args) :
290     code_(std::move(code)),
291     args_(std::move(args))
292   {}
293   result_type operator()()
294   {
295     return simgrid::xbt::apply(std::move(code_), std::move(args_));
296   }
297 };
298
299 template<class F, class... Args>
300 auto makeTask(F code, Args... args)
301 -> Task< decltype(code(std::move(args)...))() >
302 {
303   TaskImpl<F, Args...> task(std::move(code), std::make_tuple(std::move(args)...));
304   return std::move(task);
305 }
306
307 }
308 }
309
310 #endif