Logo AND Algorithmique Numérique Distribuée

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