1 /* 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 24 namespace android::mediautils { 25 26 namespace detail { 27 // Vtable interface for erased types 28 template <typename Ret, typename... Args> 29 struct 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 // Some 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 57 // - Unsafe implicit conversions of the return value to a reference type are 58 // prohibited due to the risk of dangling references (some of this safety was 59 // added to std::function in c++23). Only converting a reference to a reference to base class is 60 // permitted: 61 // std::function<Base&()> = []() -> Derived& {...} 62 // - Some (current libc++ implementation) implementations of std::function 63 // incorrectly fail to handle returning non-moveable types which is valid given 64 // mandatory copy elision. 65 // 66 // Additionally, the stored functional will use the typical rules of overload 67 // resolution to disambiguate the correct call, except, the target class will 68 // always be implicitly a non-const lvalue when called. If a different overload 69 // is preferred, wrapping the target class in a lambda with explicit casts is 70 // recommended (or using inheritance, mixins or CRTP). This avoids the 71 // complexity of utilizing abonimable function types as template params. 72 template <typename, size_t BufferSize = 32> 73 class InPlaceFunction; 74 // We partially specialize to match types which are spelled like functions 75 template <typename Ret, typename... Args, size_t BufferSize> 76 class InPlaceFunction<Ret(Args...), BufferSize> { 77 public: 78 // Storage Type Details 79 static constexpr size_t Size = BufferSize; 80 static constexpr size_t Alignment = alignof(std::max_align_t); 81 using Buffer_t = std::aligned_storage_t<Size, Alignment>; 82 template <typename T, size_t Other> 83 friend class InPlaceFunction; 84 85 private: 86 // Callable which is used for empty InPlaceFunction objects (to match the 87 // std::function interface). 88 struct BadCallable { operatorBadCallable89 [[noreturn]] Ret operator()(Args...) { std::abort(); } 90 }; 91 static_assert(std::is_trivially_destructible_v<BadCallable>); 92 93 // Implementation of vtable interface for erased types. 94 // Contains only static vtable instantiated once for each erased type and 95 // static helpers. 96 template <typename T> 97 struct TableImpl { 98 // T should be a decayed type 99 static_assert(std::is_same_v<T, std::decay_t<T>>); 100 101 // Helper functions to get an unerased reference to the type held in the 102 // buffer. std::launder is required to avoid strict aliasing rules. 103 // The cast is always defined, as a precondition for these calls is that 104 // (exactly) a T was placement new constructed into the buffer. getRefTableImpl105 constexpr static T& getRef(void* storage) { 106 return *std::launder(reinterpret_cast<T*>(storage)); 107 } 108 getRefTableImpl109 constexpr static const T& getRef(const void* storage) { 110 return *std::launder(reinterpret_cast<const T*>(storage)); 111 } 112 113 // Constexpr implies inline 114 constexpr static detail::ICallableTable<Ret, Args...> table = { 115 // Stateless lambdas are convertible to function ptrs 116 .destroy = [](void* storage) { getRef(storage).~T(); }, 117 .invoke = [](void* storage, Args&&... args) -> Ret { 118 if constexpr (std::is_void_v<Ret>) { 119 std::invoke(getRef(storage), std::forward<Args>(args)...); 120 } else { 121 return std::invoke(getRef(storage), std::forward<Args>(args)...); 122 } 123 }, 124 .copy_to = [](const void* storage, 125 void* other) { ::new (other) T(getRef(storage)); }, 126 .move_to = [](void* storage, 127 void* other) { ::new (other) T(std::move(getRef(storage))); }, 128 }; 129 }; 130 131 // Check size/align requirements for the T in Buffer_t. 132 template <typename T> 133 static constexpr bool WillFit_v = sizeof(T) <= Size && alignof(T) <= Alignment; 134 135 // Check size/align requirements for a function to function conversion 136 template <typename T> 137 static constexpr bool ConversionWillFit_v = (T::Size < Size) && (T::Alignment <= Alignment); 138 139 template <typename T> 140 struct IsInPlaceFunction : std::false_type {}; 141 142 template <size_t BufferSize_> 143 struct IsInPlaceFunction<InPlaceFunction<Ret(Args...), BufferSize_>> : std::true_type {}; 144 145 template <typename T> 146 static T BetterDeclval(); 147 template <typename T> 148 static void CheckImplicitConversion(T); 149 150 template <class T, class U, class = void> 151 struct CanImplicitConvert : std::false_type {}; 152 153 // std::is_convertible/std::invokeable has a bug (in libc++) regarding 154 // mandatory copy elision for non-moveable types. So, we roll our own. 155 // https://github.com/llvm/llvm-project/issues/55346 156 template <class From, class To> 157 struct CanImplicitConvert<From, To, 158 decltype(CheckImplicitConversion<To>(BetterDeclval<From>()))> 159 : std::true_type {}; 160 161 // Check if the provided type is a valid functional to be type-erased. 162 // if constexpr utilized for short-circuit behavior 163 template <typename T> 164 static constexpr bool isValidFunctional() { 165 using Target = std::decay_t<T>; 166 if constexpr (IsInPlaceFunction<Target>::value || std::is_same_v<Target, std::nullptr_t>) { 167 // Other overloads handle these cases 168 return false; 169 } else if constexpr (std::is_invocable_v<Target, Args...>) { 170 // The target type is a callable (with some unknown return value) 171 if constexpr (std::is_void_v<Ret>) { 172 // Any return value can be dropped to model a void returning 173 // function. 174 return WillFit_v<Target>; 175 } else { 176 using RawRet = std::invoke_result_t<Target, Args...>; 177 if constexpr (CanImplicitConvert<RawRet, Ret>::value) { 178 if constexpr (std::is_reference_v<Ret>) { 179 // If the return type is a reference, in order to 180 // avoid dangling references, we only permit functionals 181 // which return a reference to the exact type, or a base 182 // type. 183 if constexpr (std::is_reference_v<RawRet> && 184 (std::is_same_v<std::decay_t<Ret>, std::decay_t<RawRet>> || 185 std::is_base_of_v<std::decay_t<Ret>, 186 std::decay_t<RawRet>>)) { 187 return WillFit_v<Target>; 188 } 189 return false; 190 } 191 return WillFit_v<Target>; 192 } 193 // If we can't convert the raw return type, the functional is invalid. 194 return false; 195 } 196 } 197 return false; 198 } 199 200 template <typename T> 201 static constexpr bool IsValidFunctional_v = isValidFunctional<T>(); 202 // Check if the type is a strictly smaller sized InPlaceFunction 203 template <typename T> 204 static constexpr bool isConvertibleFunc() { 205 using Target = std::decay_t<T>; 206 if constexpr (IsInPlaceFunction<Target>::value) { 207 return ConversionWillFit_v<Target>; 208 } 209 return false; 210 } 211 212 template <typename T> 213 static constexpr bool IsConvertibleFunc_v = isConvertibleFunc<T>(); 214 215 // Members below 216 // This must come first for alignment 217 Buffer_t storage_; 218 const detail::ICallableTable<Ret, Args...>* vptr_; 219 220 constexpr void copy_to(InPlaceFunction& other) const { 221 vptr_->copy_to(std::addressof(storage_), std::addressof(other.storage_)); 222 other.vptr_ = vptr_; 223 } 224 225 constexpr void move_to(InPlaceFunction& other) { 226 vptr_->move_to(std::addressof(storage_), std::addressof(other.storage_)); 227 other.vptr_ = vptr_; 228 } 229 230 constexpr void destroy() { vptr_->destroy(std::addressof(storage_)); } 231 232 template <typename T, typename Target = std::decay_t<T>> 233 constexpr void genericInit(T&& t) { 234 vptr_ = &TableImpl<Target>::table; 235 ::new (std::addressof(storage_)) Target(std::forward<T>(t)); 236 } 237 238 template <typename T, typename Target = std::decay_t<T>> 239 constexpr void convertingInit(T&& smallerFunc) { 240 // Redundant, but just in-case 241 static_assert(Target::Size < Size && Target::Alignment <= Alignment); 242 if constexpr (std::is_lvalue_reference_v<T>) { 243 smallerFunc.vptr_->copy_to(std::addressof(smallerFunc.storage_), 244 std::addressof(storage_)); 245 } else { 246 smallerFunc.vptr_->move_to(std::addressof(smallerFunc.storage_), 247 std::addressof(storage_)); 248 } 249 vptr_ = smallerFunc.vptr_; 250 } 251 252 public: 253 // Public interface 254 template <typename T, std::enable_if_t<IsValidFunctional_v<T>>* = nullptr> 255 constexpr InPlaceFunction(T&& t) { 256 genericInit(std::forward<T>(t)); 257 } 258 259 // Conversion from smaller functions. 260 template <typename T, std::enable_if_t<IsConvertibleFunc_v<T>>* = nullptr> 261 constexpr InPlaceFunction(T&& t) { 262 convertingInit(std::forward<T>(t)); 263 } 264 265 constexpr InPlaceFunction(const InPlaceFunction& other) { other.copy_to(*this); } 266 267 constexpr InPlaceFunction(InPlaceFunction&& other) { other.move_to(*this); } 268 269 // Making functions default constructible has pros and cons, we will align 270 // with the standard 271 constexpr InPlaceFunction() : InPlaceFunction(BadCallable{}) {} 272 273 constexpr InPlaceFunction(std::nullptr_t) : InPlaceFunction(BadCallable{}) {} 274 275 #if __cplusplus >= 202002L 276 constexpr ~InPlaceFunction() { 277 #else 278 ~InPlaceFunction() { 279 #endif 280 destroy(); 281 } 282 283 // The std::function call operator is marked const, but this violates const 284 // correctness. We deviate from the standard and do not mark the operator as 285 // const. Collections of InPlaceFunctions should probably be mutable. 286 constexpr Ret operator()(Args... args) { 287 if constexpr (std::is_void_v<Ret>) { 288 vptr_->invoke(std::addressof(storage_), std::forward<Args>(args)...); 289 } else { 290 return vptr_->invoke(std::addressof(storage_), std::forward<Args>(args)...); 291 } 292 } 293 294 constexpr InPlaceFunction& operator=(const InPlaceFunction& other) { 295 if (std::addressof(other) == this) return *this; 296 destroy(); 297 other.copy_to(*this); 298 return *this; 299 } 300 301 constexpr InPlaceFunction& operator=(InPlaceFunction&& other) { 302 if (std::addressof(other) == this) return *this; 303 destroy(); 304 other.move_to(*this); 305 return *this; 306 } 307 308 template <typename T, std::enable_if_t<IsValidFunctional_v<T>>* = nullptr> 309 constexpr InPlaceFunction& operator=(T&& t) { 310 // We can't assign to ourselves, since T is a different type 311 destroy(); 312 genericInit(std::forward<T>(t)); 313 return *this; 314 } 315 316 // Explicitly defining this function saves a move/dtor 317 template <typename T, std::enable_if_t<IsConvertibleFunc_v<T>>* = nullptr> 318 constexpr InPlaceFunction& operator=(T&& t) { 319 // We can't assign to ourselves, since T is different type 320 destroy(); 321 convertingInit(std::forward<T>(t)); 322 return *this; 323 } 324 325 constexpr InPlaceFunction& operator=(std::nullptr_t) { return operator=(BadCallable{}); } 326 327 // Moved from InPlaceFunctions are still considered valid (similar to 328 // std::optional). If using std::move on a function object explicitly, it is 329 // recommended that the object is reset using nullptr. 330 constexpr explicit operator bool() const { return vptr_ != &TableImpl<BadCallable>::table; } 331 332 constexpr void swap(InPlaceFunction& other) { 333 if (std::addressof(other) == this) return; 334 InPlaceFunction tmp{std::move(other)}; 335 other.destroy(); 336 move_to(other); 337 destroy(); 338 tmp.move_to(*this); 339 } 340 341 friend constexpr void swap(InPlaceFunction& lhs, InPlaceFunction& rhs) { lhs.swap(rhs); } 342 }; 343 344 } // namespace android::mediautils 345