Introducing folly::Function
[folly.git] / folly / Function-inl.h
1 /*
2  * Copyright 2016 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #pragma once
18
19 namespace folly {
20 namespace detail {
21 namespace function {
22
23 // ---------------------------------------------------------------------------
24 // HELPER TYPES
25
26 enum class AllocationStatus { EMPTY, EMBEDDED, ALLOCATED };
27
28 // ---------------------------------------------------------------------------
29 // EXECUTOR CLASSES
30
31 // function::ExecutorIf
32 template <typename FunctionType>
33 class Executors<FunctionType>::ExecutorIf
34     : public Executors<FunctionType>::Traits::ExecutorMixin {
35  protected:
36   ExecutorIf(InvokeFunctionPtr invoke_ptr)
37       : Traits::ExecutorMixin(invoke_ptr){};
38
39  public:
40   // executors are neither copyable nor movable
41   ExecutorIf(ExecutorIf const&) = delete;
42   ExecutorIf& operator=(ExecutorIf const&) = delete;
43   ExecutorIf(ExecutorIf&&) = delete;
44   ExecutorIf& operator=(ExecutorIf&&) = delete;
45
46   virtual ~ExecutorIf() {}
47   virtual detail::function::AllocationStatus getAllocationStatus() const
48       noexcept = 0;
49   virtual std::pair<std::type_info const&, void*> target() const noexcept = 0;
50
51   // moveTo: move this executor to a different place
52   // preconditions:
53   // * *this is a valid executor object (derived from ExecutorIf)
54   // * the memory at [dest; dest+size) may be overwritten
55   // postconditions:
56   // * *this is an EmptyExecutor
57   // * *dest is a valid executor object (derived from ExecutorIf)
58   // You can move this executor into one for a non-const or const
59   // function.
60   virtual void moveTo(
61       typename NonConstFunctionExecutors::ExecutorIf* dest,
62       size_t size,
63       FunctionMoveCtor throws) = 0;
64   virtual void moveTo(
65       typename ConstFunctionExecutors::ExecutorIf* dest,
66       size_t size,
67       FunctionMoveCtor throws) = 0;
68 };
69
70 // function::EmptyExecutor
71 template <typename FunctionType>
72 class Executors<FunctionType>::EmptyExecutor final
73     : public Executors<FunctionType>::ExecutorIf {
74  public:
75   EmptyExecutor() noexcept : ExecutorIf(&EmptyExecutor::invokeEmpty) {}
76   ~EmptyExecutor() {}
77   detail::function::AllocationStatus getAllocationStatus() const noexcept {
78     return detail::function::AllocationStatus::EMPTY;
79   }
80
81   std::pair<std::type_info const&, void*> target() const noexcept {
82     return {typeid(void), nullptr};
83   }
84
85   template <typename DestinationExecutors>
86   void moveToImpl(typename DestinationExecutors::ExecutorIf* dest) noexcept {
87     new (dest) typename DestinationExecutors::EmptyExecutor();
88   }
89
90   void moveTo(
91       typename NonConstFunctionExecutors::ExecutorIf* dest,
92       size_t /*size*/,
93       FunctionMoveCtor /*throws*/) noexcept {
94     moveToImpl<Executors<typename Traits::NonConstFunctionType>>(dest);
95   }
96   void moveTo(
97       typename ConstFunctionExecutors::ExecutorIf* dest,
98       size_t /*size*/,
99       FunctionMoveCtor /*throws*/) noexcept {
100     moveToImpl<Executors<typename Traits::ConstFunctionType>>(dest);
101   }
102 };
103
104 // function::FunctorPtrExecutor
105 template <typename FunctionType>
106 template <typename F, typename SelectFunctionTag>
107 class Executors<FunctionType>::FunctorPtrExecutor final
108     : public Executors<FunctionType>::ExecutorIf {
109  public:
110   FunctorPtrExecutor(F&& f)
111       : ExecutorIf(
112             &FunctorPtrExecutor::template invokeFunctor<FunctorPtrExecutor>),
113         functorPtr_(new F(std::move(f))) {}
114   FunctorPtrExecutor(F const& f)
115       : ExecutorIf(
116             &FunctorPtrExecutor::template invokeFunctor<FunctorPtrExecutor>),
117         functorPtr_(new F(f)) {}
118   FunctorPtrExecutor(std::unique_ptr<F> f)
119       : ExecutorIf(
120             &FunctorPtrExecutor::template invokeFunctor<FunctorPtrExecutor>),
121         functorPtr_(std::move(f)) {}
122   ~FunctorPtrExecutor() {}
123   detail::function::AllocationStatus getAllocationStatus() const noexcept {
124     return detail::function::AllocationStatus::ALLOCATED;
125   }
126
127   static auto getFunctor(
128       typename Traits::template QualifiedPointer<ExecutorIf> self) ->
129       typename SelectFunctionTag::template QualifiedPointer<F> {
130     return FunctorPtrExecutor::selectFunctionHelper(
131         static_cast<
132             typename Traits::template QualifiedPointer<FunctorPtrExecutor>>(
133             self)
134             ->functorPtr_.get(),
135         SelectFunctionTag());
136   }
137
138   std::pair<std::type_info const&, void*> target() const noexcept {
139     return {typeid(F), const_cast<F*>(functorPtr_.get())};
140   }
141
142   template <typename DestinationExecutors>
143   void moveToImpl(typename DestinationExecutors::ExecutorIf* dest) noexcept {
144     new (dest) typename DestinationExecutors::
145         template FunctorPtrExecutor<F, SelectFunctionTag>(
146             std::move(functorPtr_));
147     this->~FunctorPtrExecutor();
148     new (this) EmptyExecutor();
149   }
150
151   void moveTo(
152       typename NonConstFunctionExecutors::ExecutorIf* dest,
153       size_t /*size*/,
154       FunctionMoveCtor /*throws*/) noexcept {
155     moveToImpl<Executors<typename Traits::NonConstFunctionType>>(dest);
156   }
157   void moveTo(
158       typename ConstFunctionExecutors::ExecutorIf* dest,
159       size_t /*size*/,
160       FunctionMoveCtor /*throws*/) noexcept {
161     moveToImpl<Executors<typename Traits::ConstFunctionType>>(dest);
162   }
163
164  private:
165   std::unique_ptr<F> functorPtr_;
166 };
167
168 // function::FunctorExecutor
169 template <typename FunctionType>
170 template <typename F, typename SelectFunctionTag>
171 class Executors<FunctionType>::FunctorExecutor final
172     : public Executors<FunctionType>::ExecutorIf {
173  public:
174   static constexpr bool kFunctorIsNTM =
175       std::is_nothrow_move_constructible<F>::value;
176
177   FunctorExecutor(F&& f)
178       : ExecutorIf(&FunctorExecutor::template invokeFunctor<FunctorExecutor>),
179         functor_(std::move(f)) {}
180   FunctorExecutor(F const& f)
181       : ExecutorIf(&FunctorExecutor::template invokeFunctor<FunctorExecutor>),
182         functor_(f) {}
183   ~FunctorExecutor() {}
184   detail::function::AllocationStatus getAllocationStatus() const noexcept {
185     return detail::function::AllocationStatus::EMBEDDED;
186   }
187
188   static auto getFunctor(
189       typename Traits::template QualifiedPointer<ExecutorIf> self) ->
190       typename SelectFunctionTag::template QualifiedPointer<F> {
191     return FunctorExecutor::selectFunctionHelper(
192         &static_cast<
193              typename Traits::template QualifiedPointer<FunctorExecutor>>(self)
194              ->functor_,
195         SelectFunctionTag());
196   }
197
198   std::pair<std::type_info const&, void*> target() const noexcept {
199     return {typeid(F), const_cast<F*>(&functor_)};
200   }
201
202   template <typename DestinationExecutors>
203   void moveToImpl(
204       typename DestinationExecutors::ExecutorIf* dest,
205       size_t size,
206       FunctionMoveCtor throws) noexcept(kFunctorIsNTM) {
207     if ((kFunctorIsNTM || throws == FunctionMoveCtor::MAY_THROW) &&
208         size >= sizeof(*this)) {
209       // Either functor_ is no-except-movable or no-except-movability is
210       // not requested *and* functor_ fits into destination
211       // => functor_ will be moved into a FunctorExecutor at dest
212       new (dest) typename DestinationExecutors::
213           template FunctorExecutor<F, SelectFunctionTag>(std::move(functor_));
214     } else {
215       // Either functor_ may throw when moved and no-except-movabilty is
216       // requested *or* the functor is too big to fit into destination
217       // => functor_ will be moved into a FunctorPtrExecutor. This will
218       // move functor_ onto the heap. The FunctorPtrExecutor object
219       // contains a unique_ptr.
220       new (dest) typename DestinationExecutors::
221           template FunctorPtrExecutor<F, SelectFunctionTag>(
222               std::move(functor_));
223     }
224     this->~FunctorExecutor();
225     new (this) EmptyExecutor();
226   }
227   void moveTo(
228       typename NonConstFunctionExecutors::ExecutorIf* dest,
229       size_t size,
230       FunctionMoveCtor throws) noexcept(kFunctorIsNTM) {
231     moveToImpl<Executors<typename Traits::NonConstFunctionType>>(
232         dest, size, throws);
233   }
234   void moveTo(
235       typename ConstFunctionExecutors::ExecutorIf* dest,
236       size_t size,
237       FunctionMoveCtor throws) noexcept(kFunctorIsNTM) {
238     moveToImpl<Executors<typename Traits::ConstFunctionType>>(
239         dest, size, throws);
240   }
241
242  private:
243   F functor_;
244 };
245 } // namespace function
246 } // namespace detail
247
248 // ---------------------------------------------------------------------------
249 // MOVE CONSTRUCTORS & MOVE ASSIGNMENT OPERATORS
250
251 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
252 Function<FunctionType, NTM, EmbedFunctorSize>::Function(
253     Function&& other) noexcept(hasNoExceptMoveCtor()) {
254   other.access<ExecutorIf>()->moveTo(access<ExecutorIf>(), kStorageSize, NTM);
255 }
256
257 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
258 Function<FunctionType, NTM, EmbedFunctorSize>&
259 Function<FunctionType, NTM, EmbedFunctorSize>::operator=(
260     Function&& rhs) noexcept(hasNoExceptMoveCtor()) {
261   destroyExecutor();
262   SCOPE_FAIL {
263     initializeEmptyExecutor();
264   };
265   rhs.access<ExecutorIf>()->moveTo(access<ExecutorIf>(), kStorageSize, NTM);
266   return *this;
267 }
268
269 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
270 template <
271     typename OtherFunctionType,
272     FunctionMoveCtor OtherNTM,
273     size_t OtherEmbedFunctorSize>
274 Function<FunctionType, NTM, EmbedFunctorSize>::
275     Function(
276         Function<OtherFunctionType,
277         OtherNTM,
278         OtherEmbedFunctorSize>&& other) noexcept(
279         OtherNTM == FunctionMoveCtor::NO_THROW &&
280         EmbedFunctorSize >= OtherEmbedFunctorSize) {
281   using OtherFunction =
282       Function<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>;
283
284   static_assert(
285       std::is_same<
286           typename Traits::NonConstFunctionType,
287           typename OtherFunction::Traits::NonConstFunctionType>::value,
288       "Function: cannot move into a Function with different "
289       "parameter signature");
290   static_assert(
291       !Traits::IsConst::value || OtherFunction::Traits::IsConst::value,
292       "Function: cannot move Function<R(Args...)> into "
293       "Function<R(Args...) const>; "
294       "use folly::constCastFunction!");
295
296   other.template access<typename OtherFunction::ExecutorIf>()->moveTo(
297       access<ExecutorIf>(), kStorageSize, NTM);
298 }
299
300 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
301 template <
302     typename OtherFunctionType,
303     FunctionMoveCtor OtherNTM,
304     size_t OtherEmbedFunctorSize>
305 Function<FunctionType, NTM, EmbedFunctorSize>&
306 Function<FunctionType, NTM, EmbedFunctorSize>::operator=(
307     Function<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>&&
308         rhs) noexcept(OtherNTM == FunctionMoveCtor::NO_THROW) {
309   using OtherFunction =
310       Function<OtherFunctionType, OtherNTM, OtherEmbedFunctorSize>;
311
312   static_assert(
313       std::is_same<
314           typename Traits::NonConstFunctionType,
315           typename OtherFunction::Traits::NonConstFunctionType>::value,
316       "Function: cannot move into a Function with different "
317       "parameter signature");
318   static_assert(
319       !Traits::IsConst::value || OtherFunction::Traits::IsConst::value,
320       "Function: cannot move Function<R(Args...)> into "
321       "Function<R(Args...) const>; "
322       "use folly::constCastFunction!");
323
324   destroyExecutor();
325   SCOPE_FAIL {
326     initializeEmptyExecutor();
327   };
328   rhs.template access<typename OtherFunction::ExecutorIf>()->moveTo(
329       access<ExecutorIf>(), kStorageSize, NTM);
330   return *this;
331 }
332
333 // ---------------------------------------------------------------------------
334 // PUBLIC METHODS
335
336 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
337 template <FunctionMoveCtor OtherNTM, size_t OtherEmbedFunctorSize>
338 inline void Function<FunctionType, NTM, EmbedFunctorSize>::
339     swap(Function<FunctionType, OtherNTM, OtherEmbedFunctorSize>& o) noexcept(
340         hasNoExceptMoveCtor() && OtherNTM == FunctionMoveCtor::NO_THROW) {
341   Function<FunctionType, NTM, EmbedFunctorSize> tmp(std::move(*this));
342   *this = std::move(o);
343   o = std::move(tmp);
344 }
345
346 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
347 Function<FunctionType, NTM, EmbedFunctorSize>::operator bool() const noexcept {
348   return access<ExecutorIf>()->getAllocationStatus() !=
349       detail::function::AllocationStatus::EMPTY;
350 }
351
352 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
353 inline bool Function<FunctionType, NTM, EmbedFunctorSize>::hasAllocatedMemory()
354     const noexcept {
355   return access<ExecutorIf>()->getAllocationStatus() ==
356       detail::function::AllocationStatus::ALLOCATED;
357 }
358
359 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
360 inline std::type_info const&
361 Function<FunctionType, NTM, EmbedFunctorSize>::target_type() const noexcept {
362   return access<ExecutorIf>()->target().first;
363 }
364
365 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
366 template <typename T>
367 T* Function<FunctionType, NTM, EmbedFunctorSize>::target() noexcept {
368   auto type_target_pair = access<ExecutorIf>()->target();
369   if (type_target_pair.first == typeid(T)) {
370     return static_cast<T*>(type_target_pair.second);
371   }
372   return nullptr;
373 }
374
375 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
376 template <typename T>
377 T const* Function<FunctionType, NTM, EmbedFunctorSize>::target() const
378     noexcept {
379   auto type_target_pair = access<ExecutorIf>()->target();
380   if (type_target_pair.first == typeid(T)) {
381     return static_cast<T const*>(type_target_pair.second);
382   }
383   return nullptr;
384 }
385
386 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
387     Function<
388         typename detail::function::FunctionTypeTraits<
389             FunctionType>::ConstFunctionType,
390         NTM,
391         EmbedFunctorSize>
392     Function<FunctionType, NTM, EmbedFunctorSize>::castToConstFunction() &&
393     noexcept(hasNoExceptMoveCtor()) {
394   using ReturnType =
395       Function<typename Traits::ConstFunctionType, NTM, EmbedFunctorSize>;
396
397   ReturnType result;
398   result.destroyExecutor();
399   SCOPE_FAIL {
400     result.initializeEmptyExecutor();
401   };
402   access<ExecutorIf>()->moveTo(
403       result.template access<typename ReturnType::ExecutorIf>(),
404       kStorageSize,
405       NTM);
406   return result;
407 }
408
409 // ---------------------------------------------------------------------------
410 // PRIVATE METHODS
411
412 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
413 template <typename T>
414 T* Function<FunctionType, NTM, EmbedFunctorSize>::access() {
415   static_assert(
416       std::is_base_of<ExecutorIf, T>::value,
417       "Function::access<T>: ExecutorIf must be base class of T "
418       "(this is a bug in the Function implementation)");
419   static_assert(
420       sizeof(T) <= kStorageSize,
421       "Requested access to object not fitting into ExecutorStore "
422       "(this is a bug in the Function implementation)");
423
424   return reinterpret_cast<T*>(&data_);
425 }
426
427 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
428 template <typename T>
429 T const* Function<FunctionType, NTM, EmbedFunctorSize>::access() const {
430   static_assert(
431       std::is_base_of<ExecutorIf, T>::value,
432       "Function::access<T>: ExecutorIf must be base class of T "
433       "(this is a bug in the Function implementation)");
434   static_assert(
435       sizeof(T) <= kStorageSize,
436       "Requested access to object not fitting into ExecutorStore "
437       "(this is a bug in the Function implementation)");
438
439   return reinterpret_cast<T const*>(&data_);
440 }
441
442 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
443 void Function<FunctionType, NTM, EmbedFunctorSize>::
444     initializeEmptyExecutor() noexcept {
445   new (access<EmptyExecutor>()) EmptyExecutor;
446 }
447
448 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
449 template <typename F>
450 void Function<FunctionType, NTM, EmbedFunctorSize>::
451     createExecutor(F&& f) noexcept(
452         noexcept(typename std::decay<F>::type(std::forward<F>(f)))) {
453   using ValueType = typename std::decay<F>::type;
454   static constexpr bool kFunctorIsNTM =
455       std::is_nothrow_move_constructible<ValueType>::value;
456   using ExecutorType = typename std::conditional<
457       (sizeof(FunctorExecutor<
458               ValueType,
459               typename Traits::DefaultSelectFunctionTag>) > kStorageSize ||
460        (hasNoExceptMoveCtor() && !kFunctorIsNTM)),
461       FunctorPtrExecutor<ValueType, typename Traits::DefaultSelectFunctionTag>,
462       FunctorExecutor<ValueType, typename Traits::DefaultSelectFunctionTag>>::
463       type;
464   new (access<ExecutorType>()) ExecutorType(std::forward<F>(f));
465 }
466
467 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
468 void Function<FunctionType, NTM, EmbedFunctorSize>::destroyExecutor() noexcept {
469   access<ExecutorIf>()->~ExecutorIf();
470 }
471
472 template <typename FunctionType, FunctionMoveCtor NTM, size_t EmbedFunctorSize>
473 struct Function<FunctionType, NTM, EmbedFunctorSize>::MinStorageSize {
474   using NotEmbeddedFunctor =
475       FunctorPtrExecutor<void(void), detail::function::SelectConstFunctionTag>;
476
477   using EmbeddedFunctor = FunctorExecutor<
478       typename std::aligned_storage<
479           constexpr_max(EmbedFunctorSize, sizeof(void (*)(void)))>::type,
480       detail::function::SelectConstFunctionTag>;
481
482   static constexpr size_t value =
483       constexpr_max(sizeof(NotEmbeddedFunctor), sizeof(EmbeddedFunctor));
484
485   static_assert(
486       sizeof(EmptyExecutor) <= value,
487       "Internal error in Function: EmptyExecutor does not fit "
488       "in storage");
489 };
490
491 } // namespace folly