libcoro  1.0
Coroutine support library for C++20
function.h
1 #pragma once
2 #include <functional>
3 #include <memory>
4 
5 namespace coro {
6 
7 
9 
22 template<typename Prototype, unsigned int reserved_space = 4*sizeof(void *)>
23 class function;
24 template<typename Prototype, bool nx, unsigned int reserved_space = 4*sizeof(void *)>
25 class function_impl;
26 
27 
28 template<typename T, typename RetVal, bool nx, typename ... Args>
29 concept IsFunctionConstructible = (nx?std::is_nothrow_invocable_r_v<RetVal,T, Args...>:std::is_invocable_r_v<RetVal, T, Args...>);
30 
31 
32 template<typename T, typename ToObj>
33 concept hasnt_cast_operator = !requires(T t) {
34  {t.operator ToObj()};
35 };
36 
37 
38 
39 
40 template<typename RetVal, bool nx, typename ... Args, unsigned int reserved_space>
41 class function_impl<RetVal(Args...), nx, reserved_space> {
42 
43  /*
44  +------------------+
45  | vtable +------|-------> dtor()
46  +------------------+ |- call()
47  | | |- move()
48  | payload | |- valid()
49  | |
50  +------------------+
51  */
52 
53 
55  class Abstract {
56  public:
58  virtual ~Abstract() = default;
60  virtual RetVal call(Args && ... args) = 0;
62 
67  virtual void move(void *address) = 0;
69 
73  virtual bool valid() const = 0;
74  };
75 
77  template<typename Fn>
78  class WrapFnSmall: public Abstract {
79  public:
80  template<std::convertible_to<Fn> Fun>
81  WrapFnSmall(Fun &&fn):_fn(std::forward<Fun>(fn)) {}
82 
83  virtual RetVal call(Args && ... args) override {
84  return _fn(std::forward<Args>(args)...);
85  }
86  virtual void move(void *address) override {
87  //allocate at address and move
88  new(address) WrapFnSmall(std::move(*this));
89  }
90  virtual bool valid() const override {return true;}
91 
92  protected:
93  //function is allocated directly in the object
94  Fn _fn;
95  };
96 
98  template<typename Fn>
99  class WrapFnLarge: public Abstract {
100  public:
101  template<std::convertible_to<Fn> Fun>
102  WrapFnLarge(Fun &&fn):_fn(std::make_unique<Fn>(std::forward<Fun>(fn))) {}
103 
104  virtual RetVal call(Args && ... args) override {
105  return (*_fn)(std::forward<Args>(args)...);
106  }
107  virtual void move(void *address) override {
108  //allocate at address and move
109  new(address) WrapFnLarge(std::move(*this));
110  }
111  virtual bool valid() const override {return true;}
112 
113  protected:
114  //pointer is held to a function allocated on the heap
115  std::unique_ptr<Fn> _fn;
116  };
117 
118  template<typename Fn>
119  class WrapFnPlacement: public Abstract {
120  public:
121  struct Deleter {
122  void operator()(Fn *fn){std::destroy_at(fn);};
123  };
124 
125  template<std::convertible_to<Fn> Fun>
126  WrapFnPlacement(Fun &&fn, void *ptr):_fn(new(ptr) Fn(std::forward<Fun>(fn))) {}
127 
128  virtual RetVal call(Args && ... args) override {
129  return (*_fn)(std::forward<Args>(args)...);
130  }
131  virtual void move(void *address) override {
132  //allocate at address and move
133  new(address) WrapFnPlacement(std::move(*this));
134  }
135  virtual bool valid() const override {return true;}
136 
137  protected:
138  //pointer is held to a function allocated on the heap
139  std::unique_ptr<Fn, Deleter> _fn;
140  };
141 
142 
144  class InvalidFn: public Abstract {
145  public:
146  virtual RetVal call(Args &&... ) override {
147  throw std::bad_function_call();
148  }
149  virtual void move(void *address) override {
150  new(address) InvalidFn(std::move(*this));
151  }
152  virtual bool valid() const override {return false;}
153  };
154 
156  struct TestCallable {
157  RetVal operator()(Args ...) noexcept(nx);
158  };
159 
160 public:
162  static_assert(IsFunctionConstructible<TestCallable, RetVal, nx, Args ...>);
164  static_assert(sizeof(WrapFnLarge<TestCallable>) <= reserved_space, "Reserved space is too small");
165 
167  function_impl() {
168  //allocate object in reserved space
169  new(_reserved) InvalidFn();
170  }
172 
175  template<IsFunctionConstructible<RetVal, nx, Args...> Fn>
176  function_impl(Fn &&fn) {
177  using Small = WrapFnSmall<std::decay_t<Fn> >;
178  using Large = WrapFnLarge<std::decay_t<Fn> >;
179  if constexpr(sizeof(Small) <= reserved_space) {
180  //allocate object in reserved space
181  new(_reserved) Small(std::forward<Fn>(fn));
182  } else {
183  //allocate object in reserved space
184  new(_reserved) Large(std::forward<Fn>(fn));
185  }
186  }
187 
189 
196  template<IsFunctionConstructible<RetVal, nx, Args...> Fn>
197  function_impl(void *ptr, Fn &&fn) {
198  new(_reserved) WrapFnPlacement<std::decay_t<Fn> >(std::forward<Fn>(fn), ptr);
199  }
200 
202 
209  function_impl(function_impl &&other) {
210  //move to current reserved space
211  other.ref().move(_reserved);
212  }
214  ~function_impl() {
215  destroy();
216  }
218 
227  function_impl &operator=(function_impl &&other) {
228  if (this != &other) {
229  destroy();
230  //move to current reserved space
231  other.ref().move(_reserved);
232  }
233  return *this;
234  }
235 
237 
241  function_impl &operator=(std::nullptr_t) {
242  if (*this) {
243  destroy();
244  new(_reserved) InvalidFn();
245  }
246  return *this;
247  }
248 
250  RetVal operator()(Args ... args) noexcept(nx) {
251  return ref().call(std::forward<Args>(args)...);
252  }
253 
255 
259  explicit operator bool() const {
260  return ref().valid();
261  }
262 
263  static constexpr bool is_noexcept = nx;
264  using value_type = RetVal;
265 
266 
267 protected:
268 
269  char _reserved[reserved_space];
270 
272  Abstract &ref() {return *reinterpret_cast<Abstract *>(_reserved);}
274  const Abstract &ref() const {return *reinterpret_cast<const Abstract *>(_reserved);}
275 
276  void destroy() {
277  //direct call of the destructor
278  ref().~Abstract();
279  }
280 
281 };
282 
283 
284 template<typename RetVal, typename ... Args, unsigned int reserved_space>
285 class function<RetVal(Args...) noexcept(false), reserved_space>: public function_impl<RetVal(Args...), false, reserved_space> {
286 public:
287  using function_impl<RetVal(Args...), false, reserved_space>::function_impl;
288 };
289 
290 template<typename RetVal, typename ... Args, unsigned int reserved_space>
291 class function<RetVal(Args...) noexcept(true), reserved_space>: public function_impl<RetVal(Args...), true, reserved_space> {
292 public:
293  using function_impl<RetVal(Args...), true, reserved_space>::function_impl;
294 };
295 
296 
297 
299 
305 template<unsigned int reserved_space = 4*sizeof(void *)>
306 class any {
307 public:
308 
310  struct content {
312  const std::type_info &type;
314  void *ptr;
316  std::size_t size;
317  };
318 
320 
324  template<hasnt_cast_operator<any> Arg>
325  any(Arg &&arg):_storage([v = Arg(std::forward<Arg>(arg))]()mutable -> content{
326  return {
327  typeid(v),
328  &v,
329  sizeof(v)
330  };
331  }) {}
332 
333 
335  any():_storage([]()mutable->content{
336  return {typeid(nullptr),nullptr,0};
337  }) {}
338 
340 
344  const content get_info() const {
345  return _storage();
346  }
348 
353  return _storage();
354  }
355 
357 
365  template<typename T>
366  const T *get_ptr() const noexcept {
367  auto ctx = get_info();
368  if (ctx.type != typeid(T)) return nullptr;
369  return reinterpret_cast<const T *>(ctx.ptr);
370  }
372 
379  template<typename T>
380  T *get_ptr() noexcept {
381  auto ctx = get_info();
382  if (ctx.type != typeid(T)) return nullptr;
383  return reinterpret_cast<T *>(ctx.ptr);
384  }
385 
387 
393  template<typename T>
394  const T &get() const {
395  auto ptr = get_ptr<T>();
396  if (ptr == nullptr) throw std::bad_cast();
397  return *ptr;
398  }
400 
406  template<typename T>
407  T &get() {
408  auto ptr = get_ptr<T>();
409  if (ptr == nullptr) throw std::bad_cast();
410  return *ptr;
411  }
412 
414 
419  bool empty() const noexcept {
420  return get_info().ptr == nullptr;
421  }
422 
424 
428  operator bool() const noexcept {
429  return get_info().ptr != nullptr;
430  }
431 
433 
438  template<typename T>
439  bool contains() const noexcept {
440  return get_info().type == typeid(T);
441  }
442 
443 protected:
444  mutable function<content()> _storage;
445 };
446 
447 
448 }
bool contains() const noexcept
Tests whether object contains given type.
Definition: function.h:439
content get_info()
Retrieve information about the content.
Definition: function.h:352
const content get_info() const
Retrieve information about the content.
Definition: function.h:344
any(Arg &&arg)
Construct any instance.
Definition: function.h:325
any()
Construct empty.
Definition: function.h:335
const T * get_ptr() const noexcept
Get as pointer.
Definition: function.h:366
const T & get() const
Get as reference.
Definition: function.h:394
T & get()
Get as reference.
Definition: function.h:407
T * get_ptr() noexcept
Get as pointer.
Definition: function.h:380
bool empty() const noexcept
Determines whether object is empty.
Definition: function.h:419
Movable any replacement with small object optimization - uses coro::function.
Definition: function.h:306
Move only function wrapper with small object optimization.
Definition: function.h:23
main namespace
Definition: aggregator.h:8
std::size_t size
size in bytes
Definition: function.h:316
void * ptr
pointer to memory, where content is located.
Definition: function.h:314
const std::type_info & type
reference to type information
Definition: function.h:312
Contains information about stored content.
Definition: function.h:310