blob: 8fa23e85e0c225b97f2557c1f7bd1e8d320daa30 [file] [log] [blame]
Atneya Naircf6ae6c2022-08-16 16:32:10 -07001/*
2 * Copyright (C) 2022 The Android Open Source Project
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#include <cstdlib>
20#include <functional>
21#include <memory>
22#include <type_traits>
23
24namespace android::mediautils {
25
26namespace detail {
27// Vtable interface for erased types
28template <typename Ret, typename... Args>
29struct ICallableTable {
30 // Destroy the erased type
31 void (*destroy)(void* storage) = nullptr;
32 // Call the erased object
33 Ret (*invoke)(void* storage, Args...) = nullptr;
34 // **Note** the next two functions only copy object data, not the vptr
35 // Copy the erased object to a new InPlaceFunction buffer
36 void (*copy_to)(const void* storage, void* other) = nullptr;
37 // Move the erased object to a new InPlaceFunction buffer
38 void (*move_to)(void* storage, void* other) = nullptr;
39};
40} // namespace detail
41
42// This class is an *almost* drop-in replacement for std::function which is guaranteed to never
43// allocate, and always holds the type erased functional object in an in-line small buffer of
44// templated size. If the object is too large to hold, the type will fail to instantiate.
45//
46// Two notable differences are:
47// - operator() is not const (unlike std::function where the call operator is
48// const even if the erased type is not const callable). This retains const
49// correctness by default. A workaround is keeping InPlaceFunction mutable.
50// - Moving from an InPlaceFunction leaves the object in a valid state (operator
51// bool remains true), similar to std::optional/std::variant.
52// Calls to the object are still defined (and are equivalent
53// to calling the underlying type after it has been moved from). To opt-out
54// (and/or ensure safety), clearing the object is recommended:
55// func1 = std::move(func2); // func2 still valid (and moved-from) after this line
56// func2 = nullptr; // calling func2 will now abort
57template <typename, size_t BufferSize = 32>
58class InPlaceFunction;
59// We partially specialize to match types which are spelled like functions
60template <typename Ret, typename... Args, size_t BufferSize>
61class InPlaceFunction<Ret(Args...), BufferSize> {
62 public:
63 // Storage Type Details
64 static constexpr size_t Size = BufferSize;
65 static constexpr size_t Alignment = alignof(std::max_align_t);
66 using Buffer_t = std::aligned_storage_t<Size, Alignment>;
67 template <typename T, size_t Other>
68 friend class InPlaceFunction;
69
70 private:
71 // Callable which is used for empty InPlaceFunction objects (to match the
72 // std::function interface).
73 struct BadCallable {
74 [[noreturn]] Ret operator()(Args...) { std::abort(); }
75 };
76 static_assert(std::is_trivially_destructible_v<BadCallable>);
77
78 // Implementation of vtable interface for erased types.
79 // Contains only static vtable instantiated once for each erased type and
80 // static helpers.
81 template <typename T>
82 struct TableImpl {
83 // T should be a decayed type
84 static_assert(std::is_same_v<T, std::decay_t<T>>);
85
86 // Helper functions to get an unerased reference to the type held in the
87 // buffer. std::launder is required to avoid strict aliasing rules.
88 // The cast is always defined, as a precondition for these calls is that
89 // (exactly) a T was placement new constructed into the buffer.
90 constexpr static T& getRef(void* storage) {
91 return *std::launder(reinterpret_cast<T*>(storage));
92 }
93
94 constexpr static const T& getRef(const void* storage) {
95 return *std::launder(reinterpret_cast<const T*>(storage));
96 }
97
98 // Constexpr implies inline
99 constexpr static detail::ICallableTable<Ret, Args...> table = {
100 // Stateless lambdas are convertible to function ptrs
101 .destroy = [](void* storage) { getRef(storage).~T(); },
102 .invoke = [](void* storage, Args... args) -> Ret {
103 return std::invoke(getRef(storage), args...);
104 },
105 .copy_to = [](const void* storage,
106 void* other) { ::new (other) T(getRef(storage)); },
107 .move_to = [](void* storage,
108 void* other) { ::new (other) T(std::move(getRef(storage))); },
109 };
110 };
111
112 // Check size/align requirements for the T in Buffer_t. We use a templated
113 // struct to enable std::conjunction (see below).
114 template <typename T>
115 struct WillFit : std::integral_constant<bool, sizeof(T) <= Size && alignof(T) <= Alignment> {};
116
117 // Check size/align requirements for a function to function conversion
118 template <typename T>
119 struct ConversionWillFit
120 : std::integral_constant<bool, (T::Size < Size) && (T::Alignment <= Alignment)> {};
121 template <typename T>
122 struct IsInPlaceFunction : std::false_type {};
123
124 template <size_t BufferSize_>
125 struct IsInPlaceFunction<InPlaceFunction<Ret(Args...), BufferSize_>> : std::true_type {};
126
127 // Pred is true iff T is a valid type to construct an InPlaceFunction with
128 // We use std::conjunction for readability and short-circuit behavior
129 // (checks are ordered).
130 // The actual target type is the decay of T.
131 template <typename T>
132 static constexpr bool Pred = std::conjunction_v<
133 std::negation<IsInPlaceFunction<std::decay_t<T>>>, // T is not also an InPlaceFunction
134 // of the same signature.
135 std::is_invocable_r<Ret, std::decay_t<T>, Args...>, // correct signature callable
136 WillFit<std::decay_t<T>> // The target type fits in local storage
137 >;
138
139 template <typename T>
140 static constexpr bool ConvertibleFunc =
141 std::conjunction_v<IsInPlaceFunction<std::decay_t<T>>, // implies correctly invokable
142 ConversionWillFit<std::decay_t<T>>>;
143
144 // Members below
145 // This must come first for alignment
146 Buffer_t storage_;
147 const detail::ICallableTable<Ret, Args...>* vptr_;
148
149 constexpr void copy_to(InPlaceFunction& other) const {
150 vptr_->copy_to(std::addressof(storage_), std::addressof(other.storage_));
151 other.vptr_ = vptr_;
152 }
153
154 constexpr void move_to(InPlaceFunction& other) {
155 vptr_->move_to(std::addressof(storage_), std::addressof(other.storage_));
156 other.vptr_ = vptr_;
157 }
158
159 constexpr void destroy() { vptr_->destroy(std::addressof(storage_)); }
160
161 template <typename T, typename Target = std::decay_t<T>>
162 constexpr void genericInit(T&& t) {
163 vptr_ = &TableImpl<Target>::table;
164 ::new (std::addressof(storage_)) Target(std::forward<T>(t));
165 }
166
167 template <typename T, typename Target = std::decay_t<T>>
168 constexpr void convertingInit(T&& smallerFunc) {
169 // Redundant, but just in-case
170 static_assert(Target::Size < Size && Target::Alignment <= Alignment);
171 if constexpr (std::is_lvalue_reference_v<T>) {
172 smallerFunc.vptr_->copy_to(std::addressof(smallerFunc.storage_),
173 std::addressof(storage_));
174 } else {
175 smallerFunc.vptr_->move_to(std::addressof(smallerFunc.storage_),
176 std::addressof(storage_));
177 }
178 vptr_ = smallerFunc.vptr_;
179 }
180
181 public:
182 // Public interface
183 template <typename T, std::enable_if_t<Pred<T>>* = nullptr>
184 constexpr InPlaceFunction(T&& t) {
185 genericInit(std::forward<T>(t));
186 }
187
188 // Conversion from smaller functions.
189 template <typename T, std::enable_if_t<ConvertibleFunc<T>>* = nullptr>
190 constexpr InPlaceFunction(T&& t) {
191 convertingInit(std::forward<T>(t));
192 }
193
194 constexpr InPlaceFunction(const InPlaceFunction& other) { other.copy_to(*this); }
195
196 constexpr InPlaceFunction(InPlaceFunction&& other) { other.move_to(*this); }
197
198 // Making functions default constructible has pros and cons, we will align
199 // with the standard
200 constexpr InPlaceFunction() : InPlaceFunction(BadCallable{}) {}
201
202 constexpr InPlaceFunction(std::nullptr_t) : InPlaceFunction(BadCallable{}) {}
203#if __cplusplus >= 202002L
204 constexpr
205#endif
206 ~InPlaceFunction() {
207 destroy();
208 }
209
210 // The std::function call operator is marked const, but this violates const
211 // correctness. We deviate from the standard and do not mark the operator as
212 // const. Collections of InPlaceFunctions should probably be mutable.
213 constexpr Ret operator()(Args... args) {
214 return vptr_->invoke(std::addressof(storage_), args...);
215 }
216
217 constexpr InPlaceFunction& operator=(const InPlaceFunction& other) {
218 if (std::addressof(other) == this) return *this;
219 destroy();
220 other.copy_to(*this);
221 return *this;
222 }
223
224 constexpr InPlaceFunction& operator=(InPlaceFunction&& other) {
225 if (std::addressof(other) == this) return *this;
226 destroy();
227 other.move_to(*this);
228 return *this;
229 }
230
231 template <typename T, std::enable_if_t<Pred<T>>* = nullptr>
232 constexpr InPlaceFunction& operator=(T&& t) {
233 // We can't assign to ourselves, since T is a different type
234 destroy();
235 genericInit(std::forward<T>(t));
236 return *this;
237 }
238
239 // Explicitly defining this function saves a move/dtor
240 template <typename T, std::enable_if_t<ConvertibleFunc<T>>* = nullptr>
241 constexpr InPlaceFunction& operator=(T&& t) {
242 // We can't assign to ourselves, since T is different type
243 destroy();
244 convertingInit(std::forward<T>(t));
245 return *this;
246 }
247
248 constexpr InPlaceFunction& operator=(std::nullptr_t) { return operator=(BadCallable{}); }
249
250 // Moved from InPlaceFunctions are still considered valid (similar to
251 // std::optional). If using std::move on a function object explicitly, it is
252 // recommended that the object is reset using nullptr.
253 constexpr explicit operator bool() const { return vptr_ != &TableImpl<BadCallable>::table; }
254
255 constexpr void swap(InPlaceFunction& other) {
256 if (std::addressof(other) == this) return;
257 InPlaceFunction tmp{std::move(other)};
258 other.destroy();
259 move_to(other);
260 destroy();
261 tmp.move_to(*this);
262 }
263
264 friend constexpr void swap(InPlaceFunction& lhs, InPlaceFunction& rhs) { lhs.swap(rhs); }
265};
266
267} // namespace android::mediautils