/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include #include /** * An instance of `Replaceable` wraps an instance of `T`. * * You access the inner `T` instance with `operator*` and `operator->` (as if * it were a smart pointer). * * `Replaceable` adds no indirection cost and performs no allocations. * * `Replaceable` has the same size and alignment as `T`. * * You can replace the `T` within a `Replaceable` using the `emplace` method * (presuming that it is constructible and destructible without throwing * exceptions). If the destructor or constructor you're using could throw an * exception you should use `Optional` instead, as it's not a logic error * for that to be empty. * * Frequently Asked Questions * ========================== * * Why does this need to be so complicated? * ---------------------------------------- * * If a `T` instance contains `const`-qualified member variables or reference * member variables we can't safely replace a `T` instance by destructing it * manually and using placement new. This is because compilers are permitted to * assume that the `const` or reference members of a named, referenced, or * pointed-to object do not change. * * For pointed-to objects in allocated storage you can use the pointer returned * by placement new or use the `launder` function to get a pointer to the new * object. Note that `launder` doesn't affect its argument, it's still * undefined behaviour to use the original pointer. And none of this helps if * the object is a local or a member variable because the destructor call will * not have been laundered. In summary, this is the only way to use placement * new that is both simple and safe: * * T* pT = new T(...); * pT->~T(); * pT = ::new (pT) T(...); * delete pT; * * What are the other safe solutions to this problem? * -------------------------------------------------- * * * Ask the designer of `T` to de-`const` and -`reference` the members of `T`. * - Makes `T` harder to reason about * - Can reduce the performance of `T` methods * - They can refuse to make the change * * Put the `T` on the heap and use a raw/unique/shared pointer. * - Adds a level of indirection, costing performance. * - Harder to reason about your code as you need to check for nullptr. * * Put the `T` in an `Optional`. * - Harder to reason about your code as you need to check for None. * * Pass the problem on, making the new code also not-replaceable * - Contagion is not really a solution * * Are there downsides to this? * ---------------------------- * * There is a potential performance penalty after converting `T` to * `Replaceable` if you have non-`T`-member-function code which repeatedly * examines the value of a `const` or `reference` data member of `T`, because * the compiler now has to look at the value each time whereas previously it * was permitted to load it once up-front and presume that it could never * change. * * Usage notes * =========== * * Don't store a reference to the `T` within a `Replaceable` unless you can * show that its lifetime does not cross an `emplace` call. For safety a * reasonable rule is to always use `operator*()` to get a fresh temporary each * time you need a `T&. * * If you store a pointer to the `T` within a `Replaceable` you **must** * launder it after each call to `emplace` before using it. Again you can * reasonably choose to always use `operator->()` to get a fresh temporary each * time you need a `T*. * * Thus far I haven't thought of a good reason to use `Replaceable` or * `Replaceable const&` as a function parameter type. * * `Replaceable&` can make sense to pass to a function that conditionally * replaces the `T`, where `T` has `const` or reference member variables. * * The main use of `Replaceable` is as a class member type or a local type * in long-running functions. * * It's probably time to rethink your design choices if you end up with * `Replaceable>`, `Optional>`, * `Replaceable>`, `unique_ptr>` etc. except as a * result of template expansion. */ namespace folly { template class Replaceable; namespace replaceable_detail { /* Mixin templates to give `replaceable` the following properties: * * 1. Trivial destructor if `T` has a trivial destructor; user-provided * otherwise * 2. Move constructor if `T` has a move constructor; deleted otherwise * 3. Move assignment operator if `T` has a move constructor; deleted * otherwise * 4. Copy constructor if `T` has a copy constructor; deleted otherwise * 5. Copy assignment operator if `T` has a copy constructor; deleted * otherwise * * Has to be done in this way because we can't `enable_if` them away */ template < class T, bool = std::is_destructible::value, bool = std::is_trivially_destructible::value> struct dtor_mixin; /* Destructible and trivially destructible */ template struct dtor_mixin {}; /* Destructible and not trivially destructible */ template struct dtor_mixin { dtor_mixin() = default; dtor_mixin(dtor_mixin&&) = default; dtor_mixin(dtor_mixin const&) = default; dtor_mixin& operator=(dtor_mixin&&) = default; dtor_mixin& operator=(dtor_mixin const&) = default; ~dtor_mixin() noexcept(std::is_nothrow_destructible::value) { T* destruct_ptr = launder(reinterpret_cast( reinterpret_cast*>(this)->storage_)); destruct_ptr->~T(); } }; /* Not destructible */ template struct dtor_mixin { dtor_mixin() = default; dtor_mixin(dtor_mixin&&) = default; dtor_mixin(dtor_mixin const&) = default; dtor_mixin& operator=(dtor_mixin&&) = default; dtor_mixin& operator=(dtor_mixin const&) = default; ~dtor_mixin() = delete; }; template < class T, bool = std::is_default_constructible::value, bool = std::is_move_constructible::value> struct default_and_move_ctor_mixin; /* Not default-constructible and not move-constructible */ template struct default_and_move_ctor_mixin { default_and_move_ctor_mixin() = delete; default_and_move_ctor_mixin(default_and_move_ctor_mixin&&) = delete; default_and_move_ctor_mixin(default_and_move_ctor_mixin const&) = default; default_and_move_ctor_mixin& operator=(default_and_move_ctor_mixin&&) = default; default_and_move_ctor_mixin& operator=(default_and_move_ctor_mixin const&) = default; protected: inline explicit default_and_move_ctor_mixin(int) {} }; /* Default-constructible and move-constructible */ template struct default_and_move_ctor_mixin { inline default_and_move_ctor_mixin() noexcept( std::is_nothrow_constructible::value) { ::new (reinterpret_cast*>(this)->storage_) T(); } inline default_and_move_ctor_mixin( default_and_move_ctor_mixin&& other) noexcept(std::is_nothrow_constructible::value) { ::new (reinterpret_cast*>(this)->storage_) T(*std::move(reinterpret_cast&>(other))); } default_and_move_ctor_mixin(default_and_move_ctor_mixin const&) = default; default_and_move_ctor_mixin& operator=(default_and_move_ctor_mixin&&) = default; inline default_and_move_ctor_mixin& operator=( default_and_move_ctor_mixin const&) = default; protected: inline explicit default_and_move_ctor_mixin(int) {} }; /* Default-constructible and not move-constructible */ template struct default_and_move_ctor_mixin { inline default_and_move_ctor_mixin() noexcept( std::is_nothrow_constructible::value) { ::new (reinterpret_cast*>(this)->storage_) T(); } default_and_move_ctor_mixin(default_and_move_ctor_mixin&&) = delete; default_and_move_ctor_mixin(default_and_move_ctor_mixin const&) = default; default_and_move_ctor_mixin& operator=(default_and_move_ctor_mixin&&) = default; default_and_move_ctor_mixin& operator=(default_and_move_ctor_mixin const&) = default; protected: inline explicit default_and_move_ctor_mixin(int) {} }; /* Not default-constructible but is move-constructible */ template struct default_and_move_ctor_mixin { default_and_move_ctor_mixin() = delete; inline default_and_move_ctor_mixin( default_and_move_ctor_mixin&& other) noexcept(std::is_nothrow_constructible::value) { ::new (reinterpret_cast*>(this)->storage_) T(*std::move(reinterpret_cast&>(other))); } default_and_move_ctor_mixin(default_and_move_ctor_mixin const&) = default; default_and_move_ctor_mixin& operator=(default_and_move_ctor_mixin&&) = default; default_and_move_ctor_mixin& operator=(default_and_move_ctor_mixin const&) = default; protected: inline explicit default_and_move_ctor_mixin(int) {} }; template < class T, bool = (std::is_destructible::value) && (std::is_move_constructible::value)> struct move_assignment_mixin; /* Not (destructible and move-constructible) */ template struct move_assignment_mixin { move_assignment_mixin() = default; move_assignment_mixin(move_assignment_mixin&&) = default; move_assignment_mixin(move_assignment_mixin const&) = default; move_assignment_mixin& operator=(move_assignment_mixin&&) = delete; move_assignment_mixin& operator=(move_assignment_mixin const&) = default; }; /* Both destructible and move-constructible */ template struct move_assignment_mixin { move_assignment_mixin() = default; move_assignment_mixin(move_assignment_mixin&&) = default; move_assignment_mixin(move_assignment_mixin const&) = default; inline move_assignment_mixin& operator=(move_assignment_mixin&& other) noexcept( std::is_nothrow_destructible::value&& std::is_nothrow_move_constructible::value) { T* destruct_ptr = launder(reinterpret_cast( reinterpret_cast*>(this)->storage_)); destruct_ptr->~T(); ::new (reinterpret_cast*>(this)->storage_) T(*std::move(reinterpret_cast&>(other))); return *this; } move_assignment_mixin& operator=(move_assignment_mixin const&) = default; }; template ::value> struct copy_ctor_mixin; /* Not copy-constructible */ template struct copy_ctor_mixin { copy_ctor_mixin() = default; copy_ctor_mixin(copy_ctor_mixin&&) = default; copy_ctor_mixin(copy_ctor_mixin const&) = delete; copy_ctor_mixin& operator=(copy_ctor_mixin&&) = default; copy_ctor_mixin& operator=(copy_ctor_mixin const&) = delete; }; /* Copy-constructible */ template struct copy_ctor_mixin { copy_ctor_mixin() = default; inline copy_ctor_mixin(copy_ctor_mixin const& other) noexcept( std::is_nothrow_constructible::value) { ::new (reinterpret_cast*>(this)->storage_) T(*reinterpret_cast const&>(other)); } copy_ctor_mixin(copy_ctor_mixin&&) = default; copy_ctor_mixin& operator=(copy_ctor_mixin&&) = default; copy_ctor_mixin& operator=(copy_ctor_mixin const&) = default; }; template < class T, bool = (std::is_destructible::value) && (std::is_copy_constructible::value)> struct copy_assignment_mixin; /* Not (destructible and copy-constructible) */ template struct copy_assignment_mixin { copy_assignment_mixin() = default; copy_assignment_mixin(copy_assignment_mixin&&) = default; copy_assignment_mixin(copy_assignment_mixin const&) = default; copy_assignment_mixin& operator=(copy_assignment_mixin&&) = default; copy_assignment_mixin& operator=(copy_assignment_mixin const&) = delete; }; /* Both destructible and copy-constructible */ template struct copy_assignment_mixin { copy_assignment_mixin() = default; copy_assignment_mixin(copy_assignment_mixin&&) = default; copy_assignment_mixin(copy_assignment_mixin const&) = default; copy_assignment_mixin& operator=(copy_assignment_mixin&&) = default; inline copy_assignment_mixin& operator=(copy_assignment_mixin const& other) noexcept( std::is_nothrow_destructible::value&& std::is_nothrow_copy_constructible::value) { T* destruct_ptr = launder(reinterpret_cast( reinterpret_cast*>(this)->storage_)); destruct_ptr->~T(); ::new (reinterpret_cast*>(this)->storage_) T(*reinterpret_cast const&>(other)); return *this; } }; template struct is_constructible_from_replaceable : bool_constant< std::is_constructible&>::value || std::is_constructible&&>::value || std::is_constructible&>::value || std::is_constructible&&>::value> {}; template struct is_convertible_from_replaceable : bool_constant< std::is_convertible&, T>::value || std::is_convertible&&, T>::value || std::is_convertible&, T>::value || std::is_convertible&&, T>::value> {}; } // namespace replaceable_detail template using is_replaceable = detail::is_instantiation_of; // Function to make a Replaceable with a type deduced from its input template constexpr Replaceable> make_replaceable(T&& t) { return Replaceable>(std::forward(t)); } template constexpr Replaceable make_replaceable(Args&&... args) { return Replaceable(in_place, std::forward(args)...); } template constexpr Replaceable make_replaceable( std::initializer_list il, Args&&... args) { return Replaceable(in_place, il, std::forward(args)...); } template class alignas(T) Replaceable : public replaceable_detail::dtor_mixin, public replaceable_detail::default_and_move_ctor_mixin, public replaceable_detail::copy_ctor_mixin, public replaceable_detail::move_assignment_mixin, public replaceable_detail::copy_assignment_mixin { using ctor_base = replaceable_detail::default_and_move_ctor_mixin; public: using value_type = T; /* Rule-of-zero default- copy- and move- constructors. The ugly code to make * these work are above, in namespace folly::replaceable_detail. */ constexpr Replaceable() = default; constexpr Replaceable(const Replaceable&) = default; constexpr Replaceable(Replaceable&&) = default; /* Rule-of-zero copy- and move- assignment operators. The ugly code to make * these work are above, in namespace folly::replaceable_detail. * * Note - these destruct the `T` and then in-place construct a new one based * on what is in the other replaceable; they do not invoke the assignment * operator of `T`. */ Replaceable& operator=(const Replaceable&) = default; Replaceable& operator=(Replaceable&&) = default; /* Rule-of-zero destructor. The ugly code to make this work is above, in * namespace folly::replaceable_detail. */ ~Replaceable() = default; /** * Constructors; these are modeled very closely on the definition of * `std::optional` in C++17. */ template < class... Args, std::enable_if_t::value, int> = 0> constexpr explicit Replaceable(in_place_t, Args&&... args) // clang-format off noexcept(std::is_nothrow_constructible::value) // clang-format on : ctor_base(0) { ::new (storage_) T(std::forward(args)...); } template < class U, class... Args, std::enable_if_t< std::is_constructible, Args&&...>::value, int> = 0> constexpr explicit Replaceable( in_place_t, std::initializer_list il, Args&&... args) // clang-format off noexcept(std::is_nothrow_constructible< T, std::initializer_list, Args&&...>::value) // clang-format on : ctor_base(0) { ::new (storage_) T(il, std::forward(args)...); } template < class U = T, std::enable_if_t< std::is_constructible::value && !std::is_same, in_place_t>::value && !std::is_same, std::decay_t>::value && std::is_convertible::value, int> = 0> constexpr /* implicit */ Replaceable(U&& other) // clang-format off noexcept(std::is_nothrow_constructible::value) // clang-format on : ctor_base(0) { ::new (storage_) T(std::forward(other)); } template < class U = T, std::enable_if_t< std::is_constructible::value && !std::is_same, in_place_t>::value && !std::is_same, std::decay_t>::value && !std::is_convertible::value, int> = 0> constexpr explicit Replaceable(U&& other) // clang-format off noexcept(std::is_nothrow_constructible::value) // clang-format on : ctor_base(0) { ::new (storage_) T(std::forward(other)); } template < class U, std::enable_if_t< std::is_constructible::value && !replaceable_detail::is_constructible_from_replaceable< T>::value && !replaceable_detail::is_convertible_from_replaceable::value && std::is_convertible::value, int> = 0> /* implicit */ Replaceable(const Replaceable& other) // clang-format off noexcept(std::is_nothrow_constructible::value) // clang-format on : ctor_base(0) { ::new (storage_) T(*other); } template < class U, std::enable_if_t< std::is_constructible::value && !replaceable_detail::is_constructible_from_replaceable< T>::value && !replaceable_detail::is_convertible_from_replaceable::value && !std::is_convertible::value, int> = 0> explicit Replaceable(const Replaceable& other) // clang-format off noexcept(std::is_nothrow_constructible::value) // clang-format on : ctor_base(0) { ::new (storage_) T(*other); } template < class U, std::enable_if_t< std::is_constructible::value && !replaceable_detail::is_constructible_from_replaceable< T>::value && !replaceable_detail::is_convertible_from_replaceable::value && std::is_convertible::value, int> = 0> /* implicit */ Replaceable(Replaceable&& other) // clang-format off noexcept(std::is_nothrow_constructible::value) // clang-format on : ctor_base(0) { ::new (storage_) T(std::move(*other)); } template < class U, std::enable_if_t< std::is_constructible::value && !replaceable_detail::is_constructible_from_replaceable< T>::value && !replaceable_detail::is_convertible_from_replaceable::value && !std::is_convertible::value, int> = 0> explicit Replaceable(Replaceable&& other) // clang-format off noexcept(std::is_nothrow_constructible::value) // clang-format on : ctor_base(0) { ::new (storage_) T(std::move(*other)); } /** * `emplace` destructs the contained object and in-place constructs the * replacement. * * The destructor must not throw (as usual). The constructor must not throw * because that would violate the invariant that a `Replaceable` always * contains a T instance. * * As these methods are `noexcept` the program will be terminated if an * exception is thrown. If you are encountering this issue you should look at * using `Optional` instead. */ template T& emplace(Args&&... args) noexcept { T* destruct_ptr = launder(reinterpret_cast(storage_)); destruct_ptr->~T(); return *::new (storage_) T(std::forward(args)...); } template T& emplace(std::initializer_list il, Args&&... args) noexcept { T* destruct_ptr = launder(reinterpret_cast(storage_)); destruct_ptr->~T(); return *::new (storage_) T(il, std::forward(args)...); } /** * `swap` just calls `swap(T&, T&)`. */ void swap(Replaceable& other) noexcept(IsNothrowSwappable{}) { using std::swap; swap(*(*this), *other); } /** * Methods to access the contained object. Intended to be very unsurprising. */ constexpr const T* operator->() const { return launder(reinterpret_cast(storage_)); } constexpr T* operator->() { return launder(reinterpret_cast(storage_)); } constexpr const T& operator*() const& { return *launder(reinterpret_cast(storage_)); } constexpr T& operator*() & { return *launder(reinterpret_cast(storage_)); } constexpr T&& operator*() && { return std::move(*launder(reinterpret_cast(storage_))); } constexpr const T&& operator*() const&& { return std::move(*launder(reinterpret_cast(storage_))); } private: friend struct replaceable_detail::dtor_mixin; friend struct replaceable_detail::default_and_move_ctor_mixin; friend struct replaceable_detail::copy_ctor_mixin; friend struct replaceable_detail::move_assignment_mixin; friend struct replaceable_detail::copy_assignment_mixin; aligned_storage_for_t storage_[1]; }; #if __cpp_deduction_guides >= 201703 template Replaceable(T)->Replaceable; #endif } // namespace folly