Replaceable
authorPhil Willoughby <philwill@fb.com>
Tue, 25 Jul 2017 07:17:24 +0000 (00:17 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Tue, 25 Jul 2017 07:24:12 +0000 (00:24 -0700)
Summary:
An instance of `Replaceable<T>` wraps an instance of `T`.

You access the inner `T` instance with `operator*` and `operator->` (as if
it were a smart pointer).

`Replaceable<T>` adds no indirection cost and performs no allocations.

`Replaceable<T>` has the same size and alignment as `T`.

You can replace the `T` within a `Replaceable<T>` 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<T>` 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
folly/Replaceable.h [new file with mode: 0644]
folly/test/ReplaceableTest.cpp [new file with mode: 0644]

index 34f6e9caa8087ba4b48f050844d5a3ecd5ff3db0..c2c8a83833e23766541d741ae7e112fbd3b8edc1 100644 (file)
@@ -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 (file)
index 0000000..3021ed2
--- /dev/null
@@ -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 <initializer_list>
+#include <new>
+#include <type_traits>
+#include <utility>
+
+#include <folly/Launder.h>
+#include <folly/Traits.h>
+#include <folly/Utility.h>
+
+/**
+ * An instance of `Replaceable<T>` wraps an instance of `T`.
+ *
+ * You access the inner `T` instance with `operator*` and `operator->` (as if
+ * it were a smart pointer).
+ *
+ * `Replaceable<T>` adds no indirection cost and performs no allocations.
+ *
+ * `Replaceable<T>` has the same size and alignment as `T`.
+ *
+ * You can replace the `T` within a `Replaceable<T>` 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<T>` 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<T>` 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<T>` 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<T>` 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<T>` or
+ * `Replaceable<T> const&` as a function parameter type.
+ *
+ * `Replaceable<T>&` 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<T>` 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<Replaceable<T>>`, `Optional<Replaceable<T>>`,
+ * `Replaceable<Optional<T>>`, `unique_ptr<Replaceable<T>>` etc. except as a
+ *  result of template expansion.
+ */
+
+namespace folly {
+template <class T>
+class Replaceable;
+
+namespace replaceable_detail {
+/* Mixin templates to give `replaceable<T>` 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 <class T>
+struct dtor_mixin<T, true, true> {};
+
+/* Destructible and not trivially destructible */
+template <class T>
+struct dtor_mixin<T, true, false> {
+  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<T>::value) {
+    T* destruct_ptr = launder(reinterpret_cast<T*>(
+        reinterpret_cast<Replaceable<T>*>(this)->storage_));
+    destruct_ptr->~T();
+  }
+};
+
+/* Not destructible */
+template <class T, bool A>
+struct dtor_mixin<T, false, A> {
+  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 <class T>
+struct default_and_move_ctor_mixin<T, false, false> {
+  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 <class T>
+struct default_and_move_ctor_mixin<T, true, true> {
+  inline default_and_move_ctor_mixin() noexcept(
+      std::is_nothrow_constructible<T>::value) {
+    ::new (reinterpret_cast<Replaceable<T>*>(this)->storage_) T();
+  }
+  inline default_and_move_ctor_mixin(
+      default_and_move_ctor_mixin&&
+          other) noexcept(std::is_nothrow_constructible<T, T&&>::value) {
+    ::new (reinterpret_cast<Replaceable<T>*>(this)->storage_)
+        T(*std::move(reinterpret_cast<Replaceable<T>&>(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 <class T>
+struct default_and_move_ctor_mixin<T, true, false> {
+  inline default_and_move_ctor_mixin() noexcept(
+      std::is_nothrow_constructible<T>::value) {
+    ::new (reinterpret_cast<Replaceable<T>*>(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 <class T>
+struct default_and_move_ctor_mixin<T, false, true> {
+  default_and_move_ctor_mixin() = delete;
+  inline default_and_move_ctor_mixin(
+      default_and_move_ctor_mixin&&
+          other) noexcept(std::is_nothrow_constructible<T, T&&>::value) {
+    ::new (reinterpret_cast<Replaceable<T>*>(this)->storage_)
+        T(*std::move(reinterpret_cast<Replaceable<T>&>(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 /* true iff destructible and move constructible */>
+struct move_assignment_mixin;
+
+/* Not (destructible and move-constructible) */
+template <class T>
+struct move_assignment_mixin<T, false> {
+  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 <class T>
+struct move_assignment_mixin<T, true> {
+  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<T>::value&&
+          std::is_nothrow_move_constructible<T>::value) {
+    T* destruct_ptr = launder(reinterpret_cast<T*>(
+        reinterpret_cast<Replaceable<T>*>(this)->storage_));
+    destruct_ptr->~T();
+    ::new (reinterpret_cast<Replaceable<T>*>(this)->storage_)
+        T(*std::move(reinterpret_cast<Replaceable<T>&>(other)));
+    return *this;
+  }
+  move_assignment_mixin& operator=(move_assignment_mixin const&) = default;
+};
+
+template <class T, bool /* true iff copy constructible */>
+struct copy_ctor_mixin;
+
+/* Not copy-constructible */
+template <class T>
+struct copy_ctor_mixin<T, false> {
+  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 <class T>
+struct copy_ctor_mixin<T, true> {
+  copy_ctor_mixin() = default;
+  inline copy_ctor_mixin(copy_ctor_mixin const& other) noexcept(
+      std::is_nothrow_constructible<T, T const&>::value) {
+    ::new (reinterpret_cast<Replaceable<T>*>(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 <class T, bool /* true iff destructible and copy constructible */>
+struct copy_assignment_mixin;
+
+/* Not (destructible and copy-constructible) */
+template <class T>
+struct copy_assignment_mixin<T, false> {
+  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 <class T>
+struct copy_assignment_mixin<T, true> {
+  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<T>::value&&
+          std::is_nothrow_copy_constructible<T>::value) {
+    T* destruct_ptr = launder(reinterpret_cast<T*>(
+        reinterpret_cast<Replaceable<T>*>(this)->storage_));
+    destruct_ptr->~T();
+    ::new (reinterpret_cast<Replaceable<T>*>(this)->storage_)
+        T(*reinterpret_cast<Replaceable<T> const&>(other));
+    return *this;
+  }
+};
+
+template <typename T>
+struct is_constructible_from_replaceable
+    : std::integral_constant<
+          bool,
+          std::is_constructible<T, Replaceable<T>&>::value ||
+              std::is_constructible<T, Replaceable<T>&&>::value ||
+              std::is_constructible<T, const Replaceable<T>&>::value ||
+              std::is_constructible<T, const Replaceable<T>&&>::value> {};
+
+template <typename T>
+constexpr bool is_constructible_from_replaceable_v{
+    is_constructible_from_replaceable<T>::value};
+
+template <typename T>
+struct is_convertible_from_replaceable
+    : std::integral_constant<
+          bool,
+          std::is_convertible<Replaceable<T>&, T>::value ||
+              std::is_convertible<Replaceable<T>&&, T>::value ||
+              std::is_convertible<const Replaceable<T>&, T>::value ||
+              std::is_convertible<const Replaceable<T>&&, T>::value> {};
+
+template <typename T>
+constexpr bool is_convertible_from_replaceable_v{
+    is_convertible_from_replaceable<T>::value};
+} // namespace replaceable_detail
+
+// Type trait template to statically test whether a type is a specialization of
+// Replaceable
+template <class T>
+struct is_replaceable : std::false_type {};
+
+template <class T>
+struct is_replaceable<Replaceable<T>> : std::true_type {};
+
+template <class T>
+constexpr bool is_replaceable_v{is_replaceable<T>::value};
+
+// Function to make a Replaceable with a type deduced from its input
+template <class T>
+constexpr Replaceable<std::decay_t<T>> make_replaceable(T&& t) {
+  return Replaceable<std::decay_t<T>>(std::forward<T>(t));
+}
+
+template <class T, class... Args>
+constexpr Replaceable<T> make_replaceable(Args&&... args) {
+  return Replaceable<T>(in_place, std::forward<Args>(args)...);
+}
+
+template <class T, class U, class... Args>
+constexpr Replaceable<T> make_replaceable(
+    std::initializer_list<U> il,
+    Args&&... args) {
+  return Replaceable<T>(in_place, il, std::forward<Args>(args)...);
+}
+
+template <class T>
+class alignas(T) Replaceable
+    : public replaceable_detail::dtor_mixin<
+          T,
+          std::is_destructible<T>::value,
+          std::is_trivially_destructible<T>::value>,
+      public replaceable_detail::default_and_move_ctor_mixin<
+          T,
+          std::is_default_constructible<T>::value,
+          std::is_move_constructible<T>::value>,
+      public replaceable_detail::
+          copy_ctor_mixin<T, std::is_copy_constructible<T>::value>,
+      public replaceable_detail::move_assignment_mixin<
+          T,
+          std::is_destructible<T>::value &&
+              std::is_move_constructible<T>::value>,
+      public replaceable_detail::copy_assignment_mixin<
+          T,
+          std::is_destructible<T>::value &&
+              std::is_copy_constructible<T>::value> {
+  using ctor_base = replaceable_detail::default_and_move_ctor_mixin<
+      T,
+      std::is_constructible<T>::value,
+      std::is_move_constructible<T>::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<std::is_constructible<T, Args&&...>::value, int> = 0>
+  constexpr explicit Replaceable(in_place_t, Args&&... args)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<T, Args&&...>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(std::forward<Args>(args)...);
+  }
+
+  template <
+      class U,
+      class... Args,
+      std::enable_if_t<
+          std::is_constructible<T, std::initializer_list<U>, Args&&...>::value,
+          int> = 0>
+  constexpr explicit Replaceable(
+      in_place_t,
+      std::initializer_list<U> il,
+      Args&&... args)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<
+          T,
+          std::initializer_list<U>,
+          Args&&...>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(il, std::forward<Args>(args)...);
+  }
+
+  template <
+      class U = T,
+      std::enable_if_t<
+          std::is_constructible<T, U&&>::value &&
+              !std::is_same<std::decay_t<U>, in_place_t>::value &&
+              !std::is_same<Replaceable<T>, std::decay_t<U>>::value &&
+              std::is_convertible<U&&, T>::value,
+          int> = 0>
+  constexpr /* implicit */ Replaceable(U&& other)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<T, U&&>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(std::forward<U>(other));
+  }
+
+  template <
+      class U = T,
+      std::enable_if_t<
+          std::is_constructible<T, U&&>::value &&
+              !std::is_same<std::decay_t<U>, in_place_t>::value &&
+              !std::is_same<Replaceable<T>, std::decay_t<U>>::value &&
+              !std::is_convertible<U&&, T>::value,
+          int> = 0>
+  explicit constexpr Replaceable(U&& other)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<T, U&&>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(std::forward<U>(other));
+  }
+
+  template <
+      class U,
+      std::enable_if_t<
+          std::is_constructible<T, const U&>::value &&
+              !replaceable_detail::is_constructible_from_replaceable_v<T> &&
+              !replaceable_detail::is_convertible_from_replaceable_v<T> &&
+              std::is_convertible<const U&, T>::value,
+          int> = 0>
+  /* implicit */ Replaceable(const Replaceable<U>& other)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<T, U const&>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(*other);
+  }
+
+  template <
+      class U,
+      std::enable_if_t<
+          std::is_constructible<T, const U&>::value &&
+              !replaceable_detail::is_constructible_from_replaceable_v<T> &&
+              !replaceable_detail::is_convertible_from_replaceable_v<T> &&
+              !std::is_convertible<const U&, T>::value,
+          int> = 0>
+  explicit Replaceable(const Replaceable<U>& other)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<T, U const&>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(*other);
+  }
+
+  template <
+      class U,
+      std::enable_if_t<
+          std::is_constructible<T, U&&>::value &&
+              !replaceable_detail::is_constructible_from_replaceable_v<T> &&
+              !replaceable_detail::is_convertible_from_replaceable_v<T> &&
+              std::is_convertible<U&&, T>::value,
+          int> = 0>
+  /* implicit */ Replaceable(Replaceable<U>&& other)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<T, U&&>::value)
+      // clang-format on
+      : ctor_base(0) {
+    ::new (storage_) T(std::move(*other));
+  }
+
+  template <
+      class U,
+      std::enable_if_t<
+          std::is_constructible<T, U&&>::value &&
+              !replaceable_detail::is_constructible_from_replaceable_v<T> &&
+              !replaceable_detail::is_convertible_from_replaceable_v<T> &&
+              !std::is_convertible<U&&, T>::value,
+          int> = 0>
+  explicit Replaceable(Replaceable<U>&& other)
+      // clang-format off
+      noexcept(std::is_nothrow_constructible<T, U&&>::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<T>` 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 <class... Args>
+  T& emplace(Args&&... args) noexcept {
+    T* destruct_ptr = launder(reinterpret_cast<T*>(storage_));
+    destruct_ptr->~T();
+    return *::new (storage_) T(std::forward<Args>(args)...);
+  }
+
+  template <class U, class... Args>
+  T& emplace(std::initializer_list<U> il, Args&&... args) noexcept {
+    T* destruct_ptr = launder(reinterpret_cast<T*>(storage_));
+    destruct_ptr->~T();
+    return *::new (storage_) T(il, std::forward<Args>(args)...);
+  }
+
+  /**
+   * `swap` just calls `swap(T&, T&)`.
+   *
+   * Should be `noexcept(std::is_nothrow_swappable<T>::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<T const*>(storage_));
+  }
+
+  constexpr T* operator->() {
+    return launder(reinterpret_cast<T*>(storage_));
+  }
+
+  constexpr const T& operator*() const & {
+    return *launder(reinterpret_cast<T const*>(storage_));
+  }
+
+  constexpr T& operator*() & {
+    return *launder(reinterpret_cast<T*>(storage_));
+  }
+
+  constexpr T&& operator*() && {
+    return std::move(*launder(reinterpret_cast<T*>(storage_)));
+  }
+
+  constexpr const T&& operator*() const && {
+    return std::move(*launder(reinterpret_cast<T const*>(storage_)));
+  }
+
+ private:
+  friend struct replaceable_detail::dtor_mixin<
+      T,
+      std::is_destructible<T>::value,
+      std::is_trivially_destructible<T>::value>;
+  friend struct replaceable_detail::default_and_move_ctor_mixin<
+      T,
+      std::is_default_constructible<T>::value,
+      std::is_move_constructible<T>::value>;
+  friend struct replaceable_detail::
+      copy_ctor_mixin<T, std::is_constructible<T, T const&>::value>;
+  friend struct replaceable_detail::move_assignment_mixin<
+      T,
+      std::is_destructible<T>::value && std::is_move_constructible<T>::value>;
+  friend struct replaceable_detail::copy_assignment_mixin<
+      T,
+      std::is_destructible<T>::value && std::is_copy_constructible<T>::value>;
+  std::aligned_storage_t<sizeof(T), alignof(T)> storage_[1];
+};
+
+#if __cplusplus > 201402L
+// C++17 allows us to define a deduction guide:
+template <class T>
+Replaceable(T)->Replaceable<T>;
+#endif
+} // namespace folly
diff --git a/folly/test/ReplaceableTest.cpp b/folly/test/ReplaceableTest.cpp
new file mode 100644 (file)
index 0000000..00eb01d
--- /dev/null
@@ -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 <folly/Replaceable.h>
+
+#include <folly/portability/GTest.h>
+
+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>, 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 <typename T>
+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 <typename T>
+struct ReplaceableStaticAttributePairTest : Test {};
+using StaticAttributePairTypes = ::testing::
+    Types<std::pair<int, long>, std::pair<OddA, OddB>, std::pair<OddB, OddA>>;
+TYPED_TEST_CASE(ReplaceableStaticAttributePairTest, StaticAttributePairTypes);
+
+TYPED_TEST(ReplaceableStaticAttributeTest, size) {
+  EXPECT_EQ(sizeof(TypeParam), sizeof(Replaceable<TypeParam>));
+}
+TYPED_TEST(ReplaceableStaticAttributeTest, align) {
+  EXPECT_EQ(alignof(TypeParam), alignof(Replaceable<TypeParam>));
+}
+TYPED_TEST(ReplaceableStaticAttributeTest, destructible) {
+  EXPECT_EQ(
+      std::is_destructible<TypeParam>::value,
+      std::is_destructible<Replaceable<TypeParam>>::value);
+}
+TYPED_TEST(ReplaceableStaticAttributeTest, trivially_destructible) {
+  EXPECT_EQ(
+      std::is_trivially_destructible<TypeParam>::value,
+      std::is_trivially_destructible<Replaceable<TypeParam>>::value);
+}
+TYPED_TEST(ReplaceableStaticAttributeTest, default_constructible) {
+  EXPECT_EQ(
+      std::is_default_constructible<TypeParam>::value,
+      std::is_default_constructible<Replaceable<TypeParam>>::value);
+}
+TYPED_TEST(ReplaceableStaticAttributeTest, move_constructible) {
+  EXPECT_EQ(
+      std::is_move_constructible<TypeParam>::value,
+      std::is_move_constructible<Replaceable<TypeParam>>::value);
+}
+TYPED_TEST(ReplaceableStaticAttributeTest, copy_constructible) {
+  EXPECT_EQ(
+      std::is_copy_constructible<TypeParam>::value,
+      std::is_copy_constructible<Replaceable<TypeParam>>::value);
+}
+TYPED_TEST(ReplaceableStaticAttributeTest, move_assignable) {
+  EXPECT_EQ(
+      std::is_move_constructible<TypeParam>::value,
+      std::is_move_assignable<Replaceable<TypeParam>>::value);
+}
+TYPED_TEST(ReplaceableStaticAttributeTest, copy_assignable) {
+  EXPECT_EQ(
+      std::is_copy_constructible<TypeParam>::value,
+      std::is_copy_assignable<Replaceable<TypeParam>>::value);
+}
+TYPED_TEST(ReplaceableStaticAttributeTest, nothrow_destructible) {
+  EXPECT_EQ(
+      std::is_nothrow_destructible<TypeParam>::value,
+      std::is_nothrow_destructible<Replaceable<TypeParam>>::value);
+}
+TYPED_TEST(ReplaceableStaticAttributeTest, nothrow_default_constructible) {
+  EXPECT_EQ(
+      std::is_nothrow_default_constructible<TypeParam>::value,
+      std::is_nothrow_default_constructible<Replaceable<TypeParam>>::value);
+}
+TYPED_TEST(ReplaceableStaticAttributeTest, nothrow_move_constructible) {
+  EXPECT_EQ(
+      std::is_nothrow_move_constructible<TypeParam>::value,
+      std::is_nothrow_move_constructible<Replaceable<TypeParam>>::value);
+}
+TYPED_TEST(ReplaceableStaticAttributeTest, nothrow_copy_constructible) {
+  EXPECT_EQ(
+      std::is_nothrow_copy_constructible<TypeParam>::value,
+      std::is_nothrow_copy_constructible<Replaceable<TypeParam>>::value);
+}
+TYPED_TEST(ReplaceableStaticAttributeTest, nothrow_move_assignable) {
+  EXPECT_EQ(
+      std::is_nothrow_destructible<TypeParam>::value &&
+          std::is_nothrow_copy_constructible<TypeParam>::value,
+      std::is_nothrow_move_assignable<Replaceable<TypeParam>>::value);
+}
+TYPED_TEST(ReplaceableStaticAttributeTest, nothrow_copy_assignable) {
+  EXPECT_EQ(
+      std::is_nothrow_destructible<TypeParam>::value &&
+          std::is_nothrow_copy_constructible<TypeParam>::value,
+      std::is_nothrow_copy_assignable<Replaceable<TypeParam>>::value);
+}
+
+TYPED_TEST(ReplaceableStaticAttributePairTest, copy_construct) {
+  using T = typename TypeParam::first_type;
+  using U = typename TypeParam::second_type;
+  EXPECT_EQ(
+      (std::is_constructible<T, U const&>::value),
+      (std::is_constructible<Replaceable<T>, Replaceable<U> const&>::value));
+}
+TYPED_TEST(ReplaceableStaticAttributePairTest, move_construct) {
+  using T = typename TypeParam::first_type;
+  using U = typename TypeParam::second_type;
+  EXPECT_EQ(
+      (std::is_constructible<T, U&&>::value),
+      (std::is_constructible<Replaceable<T>, Replaceable<U>&&>::value));
+}
+TYPED_TEST(ReplaceableStaticAttributePairTest, copy_assign) {
+  using T = typename TypeParam::first_type;
+  using U = typename TypeParam::second_type;
+  EXPECT_EQ(
+      (std::is_convertible<U, T>::value && std::is_destructible<T>::value &&
+       std::is_copy_constructible<T>::value),
+      (std::is_assignable<Replaceable<T>, Replaceable<U> const&>::value));
+}
+TYPED_TEST(ReplaceableStaticAttributePairTest, move_assign) {
+  using T = typename TypeParam::first_type;
+  using U = typename TypeParam::second_type;
+  EXPECT_EQ(
+      (std::is_convertible<U, T>::value && std::is_destructible<T>::value &&
+       std::is_move_constructible<T>::value),
+      (std::is_assignable<Replaceable<T>, Replaceable<U>&&>::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<T, U const&>::value &&
+       std::is_nothrow_destructible<T>::value),
+      (std::is_nothrow_constructible<Replaceable<T>, Replaceable<U> 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<T, U&&>::value &&
+       std::is_nothrow_destructible<T>::value),
+      (std::is_nothrow_constructible<Replaceable<T>, Replaceable<U>&&>::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<T, U const&>::value &&
+       std::is_nothrow_destructible<T>::value),
+      (std::is_nothrow_assignable<Replaceable<T>, Replaceable<U> 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<T, U&&>::value &&
+       std::is_nothrow_destructible<T>::value),
+      (std::is_nothrow_assignable<Replaceable<T>, Replaceable<U>&&>::value));
+}
+
+TEST(ReplaceableTest, Basics) {
+  auto rHasConstA = make_replaceable<HasConst>();
+  auto rHasConstB = make_replaceable<HasConst>(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<HasRef> rHasRefA{i};
+    Replaceable<HasRef> 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<OddB> rOddB{in_place, {1, 2, 3}, 4};
+  Replaceable<OddA> rOddA{std::move(rOddB)};
+  Replaceable<OddB> rOddB2{rOddA};
+}