WPILibC++ 2024.3.2-100-g4ce8f3f
FunctionExtras.h
Go to the documentation of this file.
1//===- FunctionExtras.h - Function type erasure utilities -------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8/// \file
9/// This file provides a collection of function (or more generally, callable)
10/// type erasure utilities supplementing those provided by the standard library
11/// in `<function>`.
12///
13/// It provides `unique_function`, which works like `std::function` but supports
14/// move-only callable objects and const-qualification.
15///
16/// Future plans:
17/// - Add a `function` that provides ref-qualified support, which doesn't work
18/// with `std::function`.
19/// - Provide support for specifying multiple signatures to type erase callable
20/// objects with an overload set, such as those produced by generic lambdas.
21/// - Expand to include a copyable utility that directly replaces std::function
22/// but brings the above improvements.
23///
24/// Note that LLVM's utilities are greatly simplified by not supporting
25/// allocators.
26///
27/// If the standard library ever begins to provide comparable facilities we can
28/// consider switching to those.
29///
30//===----------------------------------------------------------------------===//
31
32#ifndef WPIUTIL_WPI_FUNCTIONEXTRAS_H
33#define WPIUTIL_WPI_FUNCTIONEXTRAS_H
34
35#include "wpi/PointerIntPair.h"
36#include "wpi/PointerUnion.h"
38#include "wpi/Compiler.h"
39#include "wpi/MemAlloc.h"
40#include "wpi/type_traits.h"
41#include <cstddef>
42#include <cstring>
43#include <memory>
44#include <type_traits>
45
46namespace wpi {
47
48/// unique_function is a type-erasing functor similar to std::function.
49///
50/// It can hold move-only function objects, like lambdas capturing unique_ptrs.
51/// Accordingly, it is movable but not copyable.
52///
53/// It supports const-qualification:
54/// - unique_function<int() const> has a const operator().
55/// It can only hold functions which themselves have a const operator().
56/// - unique_function<int()> has a non-const operator().
57/// It can hold functions with a non-const operator(), like mutable lambdas.
58template <typename FunctionT> class unique_function;
59
60// GCC warns on OutOfLineStorage
61#if defined(__GNUC__) && !defined(__clang__)
62#pragma GCC diagnostic push
63#pragma GCC diagnostic ignored "-Warray-bounds"
64#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
65#endif
66
67namespace detail {
68
69template <typename T>
71 std::enable_if_t<std::is_trivially_move_constructible<T>::value &&
72 std::is_trivially_destructible<T>::value>;
73template <typename CallableT, typename ThisT>
75 std::enable_if_t<!std::is_same<remove_cvref_t<CallableT>, ThisT>::value>;
76template <typename CallableT, typename Ret, typename... Params>
77using EnableIfCallable = std::enable_if_t<std::disjunction<
78 std::is_void<Ret>,
79 std::is_same<decltype(std::declval<CallableT>()(std::declval<Params>()...)),
80 Ret>,
81 std::is_same<const decltype(std::declval<CallableT>()(
82 std::declval<Params>()...)),
83 Ret>,
84 std::is_convertible<decltype(std::declval<CallableT>()(
85 std::declval<Params>()...)),
86 Ret>>::value>;
87
88template <typename ReturnT, typename... ParamTs> class UniqueFunctionBase {
89protected:
90 static constexpr size_t InlineStorageSize = sizeof(void *) * 4;
91
92 template <typename T, class = void>
93 struct IsSizeLessThanThresholdT : std::false_type {};
94
95 template <typename T>
97 T, std::enable_if_t<sizeof(T) <= 2 * sizeof(void *)>> : std::true_type {};
98
99 // Provide a type function to map parameters that won't observe extra copies
100 // or moves and which are small enough to likely pass in register to values
101 // and all other types to l-value reference types. We use this to compute the
102 // types used in our erased call utility to minimize copies and moves unless
103 // doing so would force things unnecessarily into memory.
104 //
105 // The heuristic used is related to common ABI register passing conventions.
106 // It doesn't have to be exact though, and in one way it is more strict
107 // because we want to still be able to observe either moves *or* copies.
108 template <typename T> struct AdjustedParamTBase {
109 static_assert(!std::is_reference<T>::value,
110 "references should be handled by template specialization");
111 using type =
112 std::conditional_t<std::is_trivially_copy_constructible<T>::value &&
113 std::is_trivially_move_constructible<T>::value &&
114 IsSizeLessThanThresholdT<T>::value,
115 T, T &>;
116 };
117
118 // This specialization ensures that 'AdjustedParam<V<T>&>' or
119 // 'AdjustedParam<V<T>&&>' does not trigger a compile-time error when 'T' is
120 // an incomplete type and V a templated type.
121 template <typename T> struct AdjustedParamTBase<T &> { using type = T &; };
122 template <typename T> struct AdjustedParamTBase<T &&> { using type = T &; };
123
124 template <typename T>
125 using AdjustedParamT = typename AdjustedParamTBase<T>::type;
126
127 // The type of the erased function pointer we use as a callback to dispatch to
128 // the stored callable when it is trivial to move and destroy.
129 using CallPtrT = ReturnT (*)(void *CallableAddr,
130 AdjustedParamT<ParamTs>... Params);
131 using MovePtrT = void (*)(void *LHSCallableAddr, void *RHSCallableAddr);
132 using DestroyPtrT = void (*)(void *CallableAddr);
133
134 /// A struct to hold a single trivial callback with sufficient alignment for
135 /// our bitpacking.
136 struct alignas(8) TrivialCallback {
137 CallPtrT CallPtr;
138 };
139
140 /// A struct we use to aggregate three callbacks when we need full set of
141 /// operations.
142 struct alignas(8) NonTrivialCallbacks {
143 CallPtrT CallPtr;
144 MovePtrT MovePtr;
145 DestroyPtrT DestroyPtr;
146 };
147
148 // Create a pointer union between either a pointer to a static trivial call
149 // pointer in a struct or a pointer to a static struct of the call, move, and
150 // destroy pointers.
151 using CallbackPointerUnionT =
152 PointerUnion<TrivialCallback *, NonTrivialCallbacks *>;
153
154 // The main storage buffer. This will either have a pointer to out-of-line
155 // storage or an inline buffer storing the callable.
156 union StorageUnionT {
157 // For out-of-line storage we keep a pointer to the underlying storage and
158 // the size. This is enough to deallocate the memory.
159 struct OutOfLineStorageT {
160 void *StoragePtr;
161 size_t Size;
162 size_t Alignment;
163 } OutOfLineStorage;
164 static_assert(
165 sizeof(OutOfLineStorageT) <= InlineStorageSize,
166 "Should always use all of the out-of-line storage for inline storage!");
167
168 // For in-line storage, we just provide an aligned character buffer. We
169 // provide four pointers worth of storage here.
170 // This is mutable as an inlined `const unique_function<void() const>` may
171 // still modify its own mutable members.
172 alignas(void*) mutable std::byte InlineStorage[InlineStorageSize];
173 } StorageUnion;
174
175 // A compressed pointer to either our dispatching callback or our table of
176 // dispatching callbacks and the flag for whether the callable itself is
177 // stored inline or not.
178 PointerIntPair<CallbackPointerUnionT, 1, bool> CallbackAndInlineFlag;
179
180 bool isInlineStorage() const { return CallbackAndInlineFlag.getInt(); }
181
182 bool isTrivialCallback() const {
183 return isa<TrivialCallback *>(CallbackAndInlineFlag.getPointer());
184 }
185
186 CallPtrT getTrivialCallback() const {
187 return cast<TrivialCallback *>(CallbackAndInlineFlag.getPointer())->CallPtr;
188 }
189
190 NonTrivialCallbacks *getNonTrivialCallbacks() const {
191 return cast<NonTrivialCallbacks *>(CallbackAndInlineFlag.getPointer());
192 }
193
194 CallPtrT getCallPtr() const {
195 return isTrivialCallback() ? getTrivialCallback()
196 : getNonTrivialCallbacks()->CallPtr;
197 }
198
199 // These three functions are only const in the narrow sense. They return
200 // mutable pointers to function state.
201 // This allows unique_function<T const>::operator() to be const, even if the
202 // underlying functor may be internally mutable.
203 //
204 // const callers must ensure they're only used in const-correct ways.
205 void *getCalleePtr() const {
206 return isInlineStorage() ? getInlineStorage() : getOutOfLineStorage();
207 }
208 void *getInlineStorage() const { return &StorageUnion.InlineStorage; }
209 void *getOutOfLineStorage() const {
210 return StorageUnion.OutOfLineStorage.StoragePtr;
211 }
212
213 size_t getOutOfLineStorageSize() const {
214 return StorageUnion.OutOfLineStorage.Size;
215 }
217 return StorageUnion.OutOfLineStorage.Alignment;
218 }
219
220 void setOutOfLineStorage(void *Ptr, size_t Size, size_t Alignment) {
221 StorageUnion.OutOfLineStorage = {Ptr, Size, Alignment};
222 }
223
224 template <typename CalledAsT>
225 static ReturnT CallImpl(void *CallableAddr,
226 AdjustedParamT<ParamTs>... Params) {
227 auto &Func = *reinterpret_cast<CalledAsT *>(CallableAddr);
228 return Func(std::forward<ParamTs>(Params)...);
229 }
230
231 template <typename CallableT>
232 static void MoveImpl(void *LHSCallableAddr, void *RHSCallableAddr) noexcept {
233 new (LHSCallableAddr)
234 CallableT(std::move(*reinterpret_cast<CallableT *>(RHSCallableAddr)));
235 }
236
237 template <typename CallableT>
238 static void DestroyImpl(void *CallableAddr) noexcept {
239 reinterpret_cast<CallableT *>(CallableAddr)->~CallableT();
240 }
241
242 // The pointers to call/move/destroy functions are determined for each
243 // callable type (and called-as type, which determines the overload chosen).
244 // (definitions are out-of-line).
245
246 // By default, we need an object that contains all the different
247 // type erased behaviors needed. Create a static instance of the struct type
248 // here and each instance will contain a pointer to it.
249 // Wrap in a struct to avoid https://gcc.gnu.org/PR71954
250 template <typename CallableT, typename CalledAs, typename Enable = void>
252 static NonTrivialCallbacks Callbacks;
253 };
254 // See if we can create a trivial callback. We need the callable to be
255 // trivially moved and trivially destroyed so that we don't have to store
256 // type erased callbacks for those operations.
257 template <typename CallableT, typename CalledAs>
258 struct CallbacksHolder<CallableT, CalledAs, EnableIfTrivial<CallableT>> {
259 static TrivialCallback Callbacks;
260 };
261
262 // A simple tag type so the call-as type to be passed to the constructor.
263 template <typename T> struct CalledAs {};
264
265 // Essentially the "main" unique_function constructor, but subclasses
266 // provide the qualified type to be used for the call.
267 // (We always store a T, even if the call will use a pointer to const T).
268 template <typename CallableT, typename CalledAsT>
270 bool IsInlineStorage = true;
271 void *CallableAddr = getInlineStorage();
272 if (sizeof(CallableT) > InlineStorageSize ||
273 alignof(CallableT) > alignof(decltype(StorageUnion.InlineStorage))) {
274 IsInlineStorage = false;
275 // Allocate out-of-line storage. FIXME: Use an explicit alignment
276 // parameter in C++17 mode.
277 auto Size = sizeof(CallableT);
278 auto Alignment = alignof(CallableT);
279 CallableAddr = allocate_buffer(Size, Alignment);
280 setOutOfLineStorage(CallableAddr, Size, Alignment);
281 }
282
283 // Now move into the storage.
284 new (CallableAddr) CallableT(std::move(Callable));
285 CallbackAndInlineFlag.setPointerAndInt(
287 }
288
290 if (!CallbackAndInlineFlag.getPointer())
291 return;
292
293 // Cache this value so we don't re-check it after type-erased operations.
294 bool IsInlineStorage = isInlineStorage();
295
296 if (!isTrivialCallback())
297 getNonTrivialCallbacks()->DestroyPtr(
298 IsInlineStorage ? getInlineStorage() : getOutOfLineStorage());
299
300 if (!IsInlineStorage)
303 }
304
306 // Copy the callback and inline flag.
307 CallbackAndInlineFlag = RHS.CallbackAndInlineFlag;
308
309 // If the RHS is empty, just copying the above is sufficient.
310 if (!RHS)
311 return;
312
313 if (!isInlineStorage()) {
314 // The out-of-line case is easiest to move.
315 StorageUnion.OutOfLineStorage = RHS.StorageUnion.OutOfLineStorage;
316 } else if (isTrivialCallback()) {
317 // Move is trivial, just memcpy the bytes across.
318 memcpy(getInlineStorage(), RHS.getInlineStorage(), InlineStorageSize);
319 } else {
320 // Non-trivial move, so dispatch to a type-erased implementation.
322 RHS.getInlineStorage());
323 }
324
325 // Clear the old callback and inline flag to get back to as-if-null.
326 RHS.CallbackAndInlineFlag = {};
327
328#if !defined(NDEBUG) && !LLVM_ADDRESS_SANITIZER_BUILD
329 // In debug builds without ASan, we also scribble across the rest of the
330 // storage. Scribbling under AddressSanitizer (ASan) is disabled to prevent
331 // overwriting poisoned objects (e.g., annotated short strings).
332 memset(RHS.getInlineStorage(), 0xAD, InlineStorageSize);
333#endif
334 }
335
337 if (this == &RHS)
338 return *this;
339
340 // Because we don't try to provide any exception safety guarantees we can
341 // implement move assignment very simply by first destroying the current
342 // object and then move-constructing over top of it.
343 this->~UniqueFunctionBase();
344 new (this) UniqueFunctionBase(std::move(RHS));
345 return *this;
346 }
347
349
350public:
351 explicit operator bool() const {
352 return (bool)CallbackAndInlineFlag.getPointer();
353 }
354};
355
356template <typename R, typename... P>
357template <typename CallableT, typename CalledAsT, typename Enable>
358typename UniqueFunctionBase<R, P...>::NonTrivialCallbacks UniqueFunctionBase<
359 R, P...>::CallbacksHolder<CallableT, CalledAsT, Enable>::Callbacks = {
360 &CallImpl<CalledAsT>, &MoveImpl<CallableT>, &DestroyImpl<CallableT>};
361
362template <typename R, typename... P>
363template <typename CallableT, typename CalledAsT>
364typename UniqueFunctionBase<R, P...>::TrivialCallback
365 UniqueFunctionBase<R, P...>::CallbacksHolder<
366 CallableT, CalledAsT, EnableIfTrivial<CallableT>>::Callbacks{
367 &CallImpl<CalledAsT>};
368
369} // namespace detail
370
371template <typename R, typename... P>
372class unique_function<R(P...)> : public detail::UniqueFunctionBase<R, P...> {
373 using Base = detail::UniqueFunctionBase<R, P...>;
374
375public:
376 unique_function() = default;
377 unique_function(std::nullptr_t) {}
382
383 template <typename CallableT>
385 CallableT Callable,
388 : Base(std::forward<CallableT>(Callable),
389 typename Base::template CalledAs<CallableT>{}) {}
390
391 R operator()(P... Params) {
392 return this->getCallPtr()(this->getCalleePtr(), Params...);
393 }
394};
395
396template <typename R, typename... P>
397class unique_function<R(P...) const>
398 : public detail::UniqueFunctionBase<R, P...> {
399 using Base = detail::UniqueFunctionBase<R, P...>;
400
401public:
402 unique_function() = default;
403 unique_function(std::nullptr_t) {}
408
409 template <typename CallableT>
411 CallableT Callable,
414 : Base(std::forward<CallableT>(Callable),
415 typename Base::template CalledAs<const CallableT>{}) {}
416
417 R operator()(P... Params) const {
418 return this->getCallPtr()(this->getCalleePtr(), Params...);
419 }
420};
421
422#if defined(__GNUC__) && !defined(__clang__)
423#pragma GCC diagnostic pop
424#endif
425
426} // end namespace wpi
427
428#endif // WPIUTIL_WPI_FUNCTIONEXTRAS_H
This file defines counterparts of C library allocation functions defined in the namespace 'std'.
This file defines the PointerIntPair class.
This file defines the PointerUnion class, which is a discriminated union of pointer types.
This file contains library features backported from future STL versions.
Definition: FunctionExtras.h:88
void * getInlineStorage() const
Definition: FunctionExtras.h:208
static constexpr size_t InlineStorageSize
Definition: FunctionExtras.h:90
UniqueFunctionBase & operator=(UniqueFunctionBase &&RHS) noexcept
Definition: FunctionExtras.h:336
size_t getOutOfLineStorageSize() const
Definition: FunctionExtras.h:213
struct IsSizeLessThanThresholdT< T, std::enable_if_t< sizeof(T)<=2 *sizeof(void *)> > :std::true_type {};template< typename T > struct AdjustedParamTBase { static_assert(!std::is_reference< T >::value, "references should be handled by template specialization");using type=std::conditional_t< std::is_trivially_copy_constructible< T >::value &&std::is_trivially_move_constructible< T >::value &&IsSizeLessThanThresholdT< T >::value, T, T & >;};template< typename T > struct AdjustedParamTBase< T & > { using type=T &;};template< typename T > struct AdjustedParamTBase< T && > { using type=T &;};template< typename T > using AdjustedParamT=typename AdjustedParamTBase< T >::type;using CallPtrT=ReturnT(*)(void *CallableAddr, AdjustedParamT< ParamTs >... Params);using MovePtrT=void(*)(void *LHSCallableAddr, void *RHSCallableAddr);using DestroyPtrT=void(*)(void *CallableAddr);struct alignas(8) TrivialCallback { CallPtrT CallPtr;};struct alignas(8) NonTrivialCallbacks { CallPtrT CallPtr;MovePtrT MovePtr;DestroyPtrT DestroyPtr;};using CallbackPointerUnionT=PointerUnion< TrivialCallback *, NonTrivialCallbacks * >;union StorageUnionT { struct OutOfLineStorageT { void *StoragePtr;size_t Size;size_t Alignment;} OutOfLineStorage;static_assert(sizeof(OutOfLineStorageT)<=InlineStorageSize, "Should always use all of the out-of-line storage for inline storage!");alignas(void *) mutable std::byte InlineStorage[InlineStorageSize];} StorageUnion;PointerIntPair< CallbackPointerUnionT, 1, bool > CallbackAndInlineFlag;bool isInlineStorage() const { return CallbackAndInlineFlag.getInt();} bool isTrivialCallback() const { return isa< TrivialCallback * >(CallbackAndInlineFlag.getPointer());} CallPtrT getTrivialCallback() const { return cast< TrivialCallback * >(CallbackAndInlineFlag.getPointer()) -> CallPtr
Definition: FunctionExtras.h:96
void setOutOfLineStorage(void *Ptr, size_t Size, size_t Alignment)
Definition: FunctionExtras.h:220
NonTrivialCallbacks * getNonTrivialCallbacks() const
Definition: FunctionExtras.h:190
static ReturnT CallImpl(void *CallableAddr, AdjustedParamT< ParamTs >... Params)
Definition: FunctionExtras.h:225
UniqueFunctionBase(UniqueFunctionBase &&RHS) noexcept
Definition: FunctionExtras.h:305
size_t getOutOfLineStorageAlignment() const
Definition: FunctionExtras.h:216
UniqueFunctionBase(CallableT Callable, CalledAs< CalledAsT >)
Definition: FunctionExtras.h:269
static void DestroyImpl(void *CallableAddr) noexcept
Definition: FunctionExtras.h:238
void * getCalleePtr() const
Definition: FunctionExtras.h:205
static void MoveImpl(void *LHSCallableAddr, void *RHSCallableAddr) noexcept
Definition: FunctionExtras.h:232
CallPtrT getCallPtr() const
Definition: FunctionExtras.h:194
void * getOutOfLineStorage() const
Definition: FunctionExtras.h:209
~UniqueFunctionBase()
Definition: FunctionExtras.h:289
unique_function(const unique_function &)=delete
unique_function & operator=(unique_function &&)=default
R operator()(P... Params) const
Definition: FunctionExtras.h:417
unique_function & operator=(const unique_function &)=delete
unique_function(unique_function &&)=default
unique_function(std::nullptr_t)
Definition: FunctionExtras.h:403
unique_function(CallableT Callable, detail::EnableUnlessSameType< CallableT, unique_function > *=nullptr, detail::EnableIfCallable< const CallableT, R, P... > *=nullptr)
Definition: FunctionExtras.h:410
R operator()(P... Params)
Definition: FunctionExtras.h:391
unique_function(const unique_function &)=delete
unique_function(CallableT Callable, detail::EnableUnlessSameType< CallableT, unique_function > *=nullptr, detail::EnableIfCallable< CallableT, R, P... > *=nullptr)
Definition: FunctionExtras.h:384
unique_function(std::nullptr_t)
Definition: FunctionExtras.h:377
unique_function(unique_function &&)=default
unique_function & operator=(unique_function &&)=default
unique_function & operator=(const unique_function &)=delete
unique_function is a type-erasing functor similar to std::function.
Definition: FunctionExtras.h:58
typename std::enable_if< B, T >::type enable_if_t
Definition: core.h:271
detail namespace with internal helper functions
Definition: printf.h:61
type
Definition: core.h:573
Implement std::hash so that hash_code can be used in STL containers.
Definition: array.h:89
static constexpr const unit_t< compound_unit< energy::joules, inverse< temperature::kelvin >, inverse< substance::moles > > > R(8.3144598)
Gas constant.
std::enable_if_t< std::disjunction< std::is_void< Ret >, std::is_same< decltype(std::declval< CallableT >()(std::declval< Params >()...)), Ret >, std::is_same< const decltype(std::declval< CallableT >()(std::declval< Params >()...)), Ret >, std::is_convertible< decltype(std::declval< CallableT >()(std::declval< Params >()...)), Ret > >::value > EnableIfCallable
Definition: FunctionExtras.h:86
typename std::enable_if< E, T >::type enable_if_t
Definition: expected:233
std::enable_if_t<!std::is_same< remove_cvref_t< CallableT >, ThisT >::value > EnableUnlessSameType
Definition: FunctionExtras.h:75
std::enable_if_t< std::is_trivially_move_constructible< T >::value &&std::is_trivially_destructible< T >::value > EnableIfTrivial
Definition: FunctionExtras.h:72
Definition: ntcore_cpp.h:26
void deallocate_buffer(void *Ptr, size_t Size, size_t Alignment)
Deallocate a buffer of memory with the given size and alignment.
LLVM_ATTRIBUTE_RETURNS_NONNULL LLVM_ATTRIBUTE_RETURNS_NOALIAS void * allocate_buffer(size_t Size, size_t Alignment)
Allocate a buffer of memory with the given size and alignment.
Definition: FunctionExtras.h:251
static NonTrivialCallbacks Callbacks
Definition: FunctionExtras.h:252
Definition: FunctionExtras.h:263