From fbfd3029afb38ba11ff8f403aac3704c47141119 Mon Sep 17 00:00:00 2001 From: Phil Willoughby Date: Tue, 25 Jul 2017 00:17:24 -0700 Subject: [PATCH] Replaceable Summary: 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. Reviewed By: yfeldblum Differential Revision: D5346528 fbshipit-source-id: c7d72e73ea04e371325327a7ff0b345315d6e5ac --- folly/Makefile.am | 1 + folly/Replaceable.h | 676 +++++++++++++++++++++++++++++++++ folly/test/ReplaceableTest.cpp | 273 +++++++++++++ 3 files changed, 950 insertions(+) create mode 100644 folly/Replaceable.h create mode 100644 folly/test/ReplaceableTest.cpp diff --git a/folly/Makefile.am b/folly/Makefile.am index 34f6e9ca..c2c8a838 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -364,6 +364,7 @@ nobase_follyinclude_HEADERS = \ Random.h \ Random-inl.h \ Range.h \ + Replaceable.h \ RWSpinLock.h \ SafeAssert.h \ ScopeGuard.h \ diff --git a/folly/Replaceable.h b/folly/Replaceable.h new file mode 100644 index 00000000..3021ed24 --- /dev/null +++ b/folly/Replaceable.h @@ -0,0 +1,676 @@ +/* + * Copyright 2004-present Facebook, Inc. + * + * 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 + +/** + * 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 /* true iff destructible */, + bool /* true iff trivially destructible */> +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 /* true iff default constructible */, + bool /* true iff move constructible */> +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 +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 +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(*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 +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 + : std::integral_constant< + bool, + std::is_constructible&>::value || + std::is_constructible&&>::value || + std::is_constructible&>::value || + std::is_constructible&&>::value> {}; + +template +constexpr bool is_constructible_from_replaceable_v{ + is_constructible_from_replaceable::value}; + +template +struct is_convertible_from_replaceable + : std::integral_constant< + bool, + std::is_convertible&, T>::value || + std::is_convertible&&, T>::value || + std::is_convertible&, T>::value || + std::is_convertible&&, T>::value> {}; + +template +constexpr bool is_convertible_from_replaceable_v{ + is_convertible_from_replaceable::value}; +} // namespace replaceable_detail + +// Type trait template to statically test whether a type is a specialization of +// Replaceable +template +struct is_replaceable : std::false_type {}; + +template +struct is_replaceable> : std::true_type {}; + +template +constexpr bool is_replaceable_v{is_replaceable::value}; + +// 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< + T, + std::is_destructible::value, + std::is_trivially_destructible::value>, + public replaceable_detail::default_and_move_ctor_mixin< + T, + std::is_default_constructible::value, + std::is_move_constructible::value>, + public replaceable_detail:: + copy_ctor_mixin::value>, + public replaceable_detail::move_assignment_mixin< + T, + std::is_destructible::value && + std::is_move_constructible::value>, + public replaceable_detail::copy_assignment_mixin< + T, + std::is_destructible::value && + std::is_copy_constructible::value> { + using ctor_base = replaceable_detail::default_and_move_ctor_mixin< + T, + std::is_constructible::value, + std::is_move_constructible::value>; + + 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> + explicit constexpr 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_v && + !replaceable_detail::is_convertible_from_replaceable_v && + 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_v && + !replaceable_detail::is_convertible_from_replaceable_v && + !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_v && + !replaceable_detail::is_convertible_from_replaceable_v && + 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_v && + !replaceable_detail::is_convertible_from_replaceable_v && + !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&)`. + * + * Should be `noexcept(std::is_nothrow_swappable::value)` but we don't + * depend on C++17 features. + */ + void swap(Replaceable& other) { + 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< + T, + std::is_destructible::value, + std::is_trivially_destructible::value>; + friend struct replaceable_detail::default_and_move_ctor_mixin< + T, + std::is_default_constructible::value, + std::is_move_constructible::value>; + friend struct replaceable_detail:: + copy_ctor_mixin::value>; + friend struct replaceable_detail::move_assignment_mixin< + T, + std::is_destructible::value && std::is_move_constructible::value>; + friend struct replaceable_detail::copy_assignment_mixin< + T, + std::is_destructible::value && std::is_copy_constructible::value>; + std::aligned_storage_t storage_[1]; +}; + +#if __cplusplus > 201402L +// C++17 allows us to define a deduction guide: +template +Replaceable(T)->Replaceable; +#endif +} // namespace folly diff --git a/folly/test/ReplaceableTest.cpp b/folly/test/ReplaceableTest.cpp new file mode 100644 index 00000000..00eb01d9 --- /dev/null +++ b/folly/test/ReplaceableTest.cpp @@ -0,0 +1,273 @@ +/* + * Copyright 2004-present Facebook, Inc. + * + * 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. + */ + +#include + +#include + +using namespace ::testing; +using namespace ::folly; + +namespace { +struct alignas(128) BigAlign {}; +struct HasConst final { + bool const b1; + HasConst() noexcept : b1(true) {} + explicit HasConst(bool b) noexcept : b1(b) {} + HasConst(HasConst const& b) noexcept : b1(b.b1) {} + HasConst(HasConst&& b) noexcept : b1(b.b1) {} + HasConst& operator=(HasConst const&) = delete; + HasConst& operator=(HasConst&&) = delete; +}; +struct HasRef final { + int& i1; + explicit HasRef(int& i) noexcept(false) : i1(i) {} + HasRef(HasRef const& i) noexcept(false) : i1(i.i1) {} + HasRef(HasRef&& i) noexcept(false) : i1(i.i1) {} + HasRef& operator=(HasRef const&) = delete; + HasRef& operator=(HasRef&&) = delete; + ~HasRef() noexcept(false) { + ++i1; + } +}; + +struct OddA; +struct OddB { + OddB() = delete; + OddB(std::initializer_list, int) noexcept(false) {} + explicit OddB(OddA&&) {} + explicit OddB(OddA const&) noexcept(false) {} + OddB(OddB&&) = delete; + OddB(OddB const&) = delete; + OddB& operator=(OddB&&) = delete; + OddB& operator=(OddB const&) = delete; + ~OddB() = default; +}; +struct OddA { + OddA() = delete; + explicit OddA(OddB&&) noexcept {} + explicit OddA(OddB const&) = delete; + OddA(OddA&&) = delete; + OddA(OddA const&) = delete; + OddA& operator=(OddA&&) = delete; + OddA& operator=(OddA const&) = delete; + ~OddA() noexcept(false) {} +}; +struct Indestructible { + ~Indestructible() = delete; +}; +} // anonymous namespace + +template +struct ReplaceableStaticAttributeTest : Test {}; +using StaticAttributeTypes = ::testing::Types< + char, + short, + int, + long, + float, + double, + char[11], + BigAlign, + HasConst, + HasRef, + OddA, + OddB, + Indestructible>; +TYPED_TEST_CASE(ReplaceableStaticAttributeTest, StaticAttributeTypes); + +template +struct ReplaceableStaticAttributePairTest : Test {}; +using StaticAttributePairTypes = ::testing:: + Types, std::pair, std::pair>; +TYPED_TEST_CASE(ReplaceableStaticAttributePairTest, StaticAttributePairTypes); + +TYPED_TEST(ReplaceableStaticAttributeTest, size) { + EXPECT_EQ(sizeof(TypeParam), sizeof(Replaceable)); +} +TYPED_TEST(ReplaceableStaticAttributeTest, align) { + EXPECT_EQ(alignof(TypeParam), alignof(Replaceable)); +} +TYPED_TEST(ReplaceableStaticAttributeTest, destructible) { + EXPECT_EQ( + std::is_destructible::value, + std::is_destructible>::value); +} +TYPED_TEST(ReplaceableStaticAttributeTest, trivially_destructible) { + EXPECT_EQ( + std::is_trivially_destructible::value, + std::is_trivially_destructible>::value); +} +TYPED_TEST(ReplaceableStaticAttributeTest, default_constructible) { + EXPECT_EQ( + std::is_default_constructible::value, + std::is_default_constructible>::value); +} +TYPED_TEST(ReplaceableStaticAttributeTest, move_constructible) { + EXPECT_EQ( + std::is_move_constructible::value, + std::is_move_constructible>::value); +} +TYPED_TEST(ReplaceableStaticAttributeTest, copy_constructible) { + EXPECT_EQ( + std::is_copy_constructible::value, + std::is_copy_constructible>::value); +} +TYPED_TEST(ReplaceableStaticAttributeTest, move_assignable) { + EXPECT_EQ( + std::is_move_constructible::value, + std::is_move_assignable>::value); +} +TYPED_TEST(ReplaceableStaticAttributeTest, copy_assignable) { + EXPECT_EQ( + std::is_copy_constructible::value, + std::is_copy_assignable>::value); +} +TYPED_TEST(ReplaceableStaticAttributeTest, nothrow_destructible) { + EXPECT_EQ( + std::is_nothrow_destructible::value, + std::is_nothrow_destructible>::value); +} +TYPED_TEST(ReplaceableStaticAttributeTest, nothrow_default_constructible) { + EXPECT_EQ( + std::is_nothrow_default_constructible::value, + std::is_nothrow_default_constructible>::value); +} +TYPED_TEST(ReplaceableStaticAttributeTest, nothrow_move_constructible) { + EXPECT_EQ( + std::is_nothrow_move_constructible::value, + std::is_nothrow_move_constructible>::value); +} +TYPED_TEST(ReplaceableStaticAttributeTest, nothrow_copy_constructible) { + EXPECT_EQ( + std::is_nothrow_copy_constructible::value, + std::is_nothrow_copy_constructible>::value); +} +TYPED_TEST(ReplaceableStaticAttributeTest, nothrow_move_assignable) { + EXPECT_EQ( + std::is_nothrow_destructible::value && + std::is_nothrow_copy_constructible::value, + std::is_nothrow_move_assignable>::value); +} +TYPED_TEST(ReplaceableStaticAttributeTest, nothrow_copy_assignable) { + EXPECT_EQ( + std::is_nothrow_destructible::value && + std::is_nothrow_copy_constructible::value, + std::is_nothrow_copy_assignable>::value); +} + +TYPED_TEST(ReplaceableStaticAttributePairTest, copy_construct) { + using T = typename TypeParam::first_type; + using U = typename TypeParam::second_type; + EXPECT_EQ( + (std::is_constructible::value), + (std::is_constructible, Replaceable const&>::value)); +} +TYPED_TEST(ReplaceableStaticAttributePairTest, move_construct) { + using T = typename TypeParam::first_type; + using U = typename TypeParam::second_type; + EXPECT_EQ( + (std::is_constructible::value), + (std::is_constructible, Replaceable&&>::value)); +} +TYPED_TEST(ReplaceableStaticAttributePairTest, copy_assign) { + using T = typename TypeParam::first_type; + using U = typename TypeParam::second_type; + EXPECT_EQ( + (std::is_convertible::value && std::is_destructible::value && + std::is_copy_constructible::value), + (std::is_assignable, Replaceable const&>::value)); +} +TYPED_TEST(ReplaceableStaticAttributePairTest, move_assign) { + using T = typename TypeParam::first_type; + using U = typename TypeParam::second_type; + EXPECT_EQ( + (std::is_convertible::value && std::is_destructible::value && + std::is_move_constructible::value), + (std::is_assignable, Replaceable&&>::value)); +} +TYPED_TEST(ReplaceableStaticAttributePairTest, nothrow_copy_construct) { + using T = typename TypeParam::first_type; + using U = typename TypeParam::second_type; + EXPECT_EQ( + (std::is_nothrow_constructible::value && + std::is_nothrow_destructible::value), + (std::is_nothrow_constructible, Replaceable const&>:: + value)); +} +TYPED_TEST(ReplaceableStaticAttributePairTest, nothrow_move_construct) { + using T = typename TypeParam::first_type; + using U = typename TypeParam::second_type; + EXPECT_EQ( + (std::is_nothrow_constructible::value && + std::is_nothrow_destructible::value), + (std::is_nothrow_constructible, Replaceable&&>::value)); +} +TYPED_TEST(ReplaceableStaticAttributePairTest, nothrow_copy_assign) { + using T = typename TypeParam::first_type; + using U = typename TypeParam::second_type; + EXPECT_EQ( + (std::is_nothrow_constructible::value && + std::is_nothrow_destructible::value), + (std::is_nothrow_assignable, Replaceable const&>:: + value)); +} +TYPED_TEST(ReplaceableStaticAttributePairTest, nothrow_move_assign) { + using T = typename TypeParam::first_type; + using U = typename TypeParam::second_type; + EXPECT_EQ( + (std::is_nothrow_constructible::value && + std::is_nothrow_destructible::value), + (std::is_nothrow_assignable, Replaceable&&>::value)); +} + +TEST(ReplaceableTest, Basics) { + auto rHasConstA = make_replaceable(); + auto rHasConstB = make_replaceable(false); + EXPECT_TRUE(rHasConstA->b1); + EXPECT_FALSE(rHasConstB->b1); + rHasConstA = rHasConstB; + EXPECT_FALSE(rHasConstA->b1); + EXPECT_FALSE(rHasConstB->b1); + rHasConstB.emplace(true); + EXPECT_FALSE(rHasConstA->b1); + EXPECT_TRUE(rHasConstB->b1); + rHasConstA = std::move(rHasConstB); + EXPECT_TRUE(rHasConstA->b1); + EXPECT_TRUE(rHasConstB->b1); +} + +TEST(ReplaceableTest, DestructsWhenExpected) { + int i{0}; + { + Replaceable rHasRefA{i}; + Replaceable rHasRefB{i}; + EXPECT_EQ(0, i); + rHasRefA = rHasRefB; + EXPECT_EQ(1, i); + rHasRefB.emplace(i); + EXPECT_EQ(2, i); + rHasRefA = std::move(rHasRefB); + EXPECT_EQ(3, i); + } + EXPECT_EQ(5, i); +} + +TEST(ReplaceableTest, Conversions) { + Replaceable rOddB{in_place, {1, 2, 3}, 4}; + Replaceable rOddA{std::move(rOddB)}; + Replaceable rOddB2{rOddA}; +} -- 2.34.1