// Copyright 2019 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef LIB_STDCOMPAT_INTERNAL_STORAGE_H_ #define LIB_STDCOMPAT_INTERNAL_STORAGE_H_ #include #include #include #include #include #include "utility.h" namespace cpp17 { namespace internal { // Type tag to select overloads based on type T. template struct type_tag { using type = T; }; // Type tag to select overloads based on index Index. template struct index_tag { static constexpr std::size_t index = Index; }; // Type tag to select trivial initialization. enum trivial_init_t { trivial_init_v }; // Type tag to select default initialization. enum default_init_t { default_init_v }; // Type tag to select conditional initialization. enum maybe_init_t { maybe_init_v }; // Represents the pair (T, Index) in the type system. template struct type_index {}; // Represents whether a type is trivially/non-trivially destructible. enum class destructor_class { trivial, non_trivial, }; // Represents whether a type is trivially/non-trivially copyable. enum class copy_class { trivial, non_trivial, }; // Represents whether a type is trivially/non-trivially movable. enum class move_class { trivial, non_trivial, }; // Represents the full complement of move/copy/destruct classes for a type. template struct storage_class {}; template using make_storage_class = storage_class ? destructor_class::trivial : destructor_class::non_trivial, is_trivially_copyable_v ? copy_class::trivial : copy_class::non_trivial, is_trivially_movable_v ? move_class::trivial : move_class::non_trivial>; // A trivial type for the empty alternative of union-based storage. struct empty_type {}; // Index type used to track the active variant. Tracking uses zero-based // indices. Empty is denoted by the maximum representable value. using index_type = std::size_t; // Index denoting that no user-specified variant is active. Take care not to // ODR-use this value. constexpr index_type empty_index = std::numeric_limits::max(); #ifdef NDEBUG #define LIB_STDCOMPAT_INTERNAL_UNREACHABLE_OR_ABORT __builtin_unreachable #else #define LIB_STDCOMPAT_INTERNAL_UNREACHABLE_OR_ABORT __builtin_abort #endif // Base type for lazy-initialized union storage types. This type implements a // recursive union of the element types in Ts. Specializations handle the // recursive and terminal cases, and the different storage requirements for // trivially/non-trivially destructible types. template union storage_base; // Non-trivial terminal case. template <> union storage_base> { storage_base() : empty{} {} template storage_base(type_tag, Args&&...) : empty{} {} template storage_base(index_tag, Args&&...) : empty{} {} // Non-trivial destructor. ~storage_base() {} storage_base(const storage_base&) = default; storage_base(storage_base&&) = default; storage_base& operator=(const storage_base&) = default; storage_base& operator=(storage_base&&) = default; void construct_at(std::size_t index, const storage_base&) { if (index == empty_index) { new (&empty) empty_type{}; } else { LIB_STDCOMPAT_INTERNAL_UNREACHABLE_OR_ABORT(); } } void construct_at(std::size_t index, storage_base&&) { if (index == empty_index) { new (&empty) empty_type{}; } else { LIB_STDCOMPAT_INTERNAL_UNREACHABLE_OR_ABORT(); } } void assign_at(std::size_t index, const storage_base& other) { if (index == empty_index) { empty = other.empty; } else { LIB_STDCOMPAT_INTERNAL_UNREACHABLE_OR_ABORT(); } } void assign_at(std::size_t index, storage_base&& other) { if (index == empty_index) { empty = std::move(other.empty); } else { LIB_STDCOMPAT_INTERNAL_UNREACHABLE_OR_ABORT(); } } void swap_at(std::size_t index, storage_base& other) { if (index == empty_index) { using std::swap; swap(empty, other.empty); } else { LIB_STDCOMPAT_INTERNAL_UNREACHABLE_OR_ABORT(); } } template std::size_t construct(type_tag, Args&&...) { new (&empty) empty_type{}; return empty_index; } template std::size_t construct(index_tag, Args&&...) { new (&empty) empty_type{}; return empty_index; } void reset(std::size_t index) { if (index == empty_index) { empty.empty_type::~empty_type(); } else { LIB_STDCOMPAT_INTERNAL_UNREACHABLE_OR_ABORT(); } } empty_type& get(type_tag) { return empty; } const empty_type& get(type_tag) const { return empty; } empty_type& get(index_tag) { return empty; } const empty_type& get(index_tag) const { return empty; } std::size_t index(type_tag) const { return empty_index; } template bool visit(std::size_t, V&&) { return false; } template bool visit(std::size_t, V&&) const { return false; } empty_type empty; }; // Trivial terminal case. template <> union storage_base> { constexpr storage_base() : empty{} {} template constexpr storage_base(type_tag, Args&&...) : empty{} {} template constexpr storage_base(index_tag, Args&&...) : empty{} {} // Trivial destructor. ~storage_base() = default; constexpr storage_base(const storage_base&) = default; constexpr storage_base(storage_base&&) = default; constexpr storage_base& operator=(const storage_base&) = default; constexpr storage_base& operator=(storage_base&&) = default; constexpr void construct_at(std::size_t index, const storage_base&) { if (index == empty_index) { new (&empty) empty_type{}; } else { LIB_STDCOMPAT_INTERNAL_UNREACHABLE_OR_ABORT(); } } constexpr void construct_at(std::size_t index, storage_base&&) { if (index == empty_index) { new (&empty) empty_type{}; } else { LIB_STDCOMPAT_INTERNAL_UNREACHABLE_OR_ABORT(); } } constexpr void assign_at(std::size_t index, const storage_base& other) { if (index == empty_index) { empty = other.empty; } else { LIB_STDCOMPAT_INTERNAL_UNREACHABLE_OR_ABORT(); } } constexpr void assign_at(std::size_t index, storage_base&& other) { if (index == empty_index) { empty = std::move(other.empty); } else { LIB_STDCOMPAT_INTERNAL_UNREACHABLE_OR_ABORT(); } } constexpr void swap_at(std::size_t index, storage_base& other) { if (index == empty_index) { using std::swap; swap(empty, other.empty); } else { LIB_STDCOMPAT_INTERNAL_UNREACHABLE_OR_ABORT(); } } template constexpr std::size_t construct(type_tag, Args&&...) { new (&empty) empty_type{}; return empty_index; } template constexpr std::size_t construct(index_tag, Args&&...) { new (&empty) empty_type{}; return empty_index; } constexpr void reset(std::size_t index) { if (index == empty_index) { empty.empty_type::~empty_type(); } else { LIB_STDCOMPAT_INTERNAL_UNREACHABLE_OR_ABORT(); } } constexpr empty_type& get(type_tag) { return empty; } constexpr const empty_type& get(type_tag) const { return empty; } constexpr empty_type& get(index_tag) { return empty; } constexpr const empty_type& get(index_tag) const { return empty; } constexpr std::size_t index(type_tag) const { return empty_index; } template constexpr bool visit(std::size_t, V&&) { return false; } template constexpr bool visit(std::size_t, V&&) const { return false; } empty_type empty; }; template union storage_base, type_index...> { storage_base() : empty{} {} template storage_base(type_tag, Args&&... args) : value(std::forward(args)...) {} template storage_base(index_tag, Args&&... args) : value(std::forward(args)...) {} template storage_base(type_tag, Args&&... args) : rest(type_tag{}, std::forward(args)...) {} template storage_base(index_tag, Args&&... args) : rest(index_tag{}, std::forward(args)...) {} // Non-trivial destructor. ~storage_base() {} // Trival copy/move construction and assignment. storage_base(const storage_base&) = default; storage_base(storage_base&&) = default; storage_base& operator=(const storage_base&) = default; storage_base& operator=(storage_base&&) = default; void construct_at(std::size_t index, const storage_base& other) { if (index == Index) { new (&value) T{other.value}; } else { rest.construct_at(index, other.rest); } } void construct_at(std::size_t index, storage_base&& other) { if (index == Index) { new (&value) T{std::move(other.value)}; } else { rest.construct_at(index, std::move(other.rest)); } } void assign_at(std::size_t index, const storage_base& other) { if (index == Index) { value = other.value; } else { rest.assign_at(index, other.rest); } } void assign_at(std::size_t index, storage_base&& other) { if (index == Index) { value = std::move(other.value); } else { rest.assign_at(index, std::move(other.rest)); } } void swap_at(std::size_t index, storage_base& other) { if (index == Index) { using std::swap; swap(value, other.value); } else { rest.swap_at(index, other.rest); } } template std::size_t construct(type_tag, Args&&... args) { new (&value) T(std::forward(args)...); return Index; } template std::size_t construct(type_tag, Args&&... args) { return rest.construct(type_tag{}, std::forward(args)...); } template std::size_t construct(index_tag, Args&&... args) { new (&value) T(std::forward(args)...); return Index; } template std::size_t construct(index_tag, Args&&... args) { return rest.construct(index_tag{}, std::forward(args)...); } void reset(std::size_t index) { if (index == Index) { value.~T(); } else { rest.reset(index); } } T& get(type_tag) { return value; } const T& get(type_tag) const { return value; } template U& get(type_tag) { return rest.get(type_tag{}); } template const U& get(type_tag) const { return rest.get(type_tag{}); } T& get(index_tag) { return value; } const T& get(index_tag) const { return value; } template auto& get(index_tag) { return rest.get(index_tag{}); } template const auto& get(index_tag) const { return rest.get(index_tag{}); } std::size_t index(type_tag) const { return Index; } template std::size_t index(type_tag) const { return rest.index(type_tag{}); } template bool visit(std::size_t index, V&& visitor) { if (index == Index) { std::forward(visitor)(type_tag{}, index_tag{}, this); return true; } else { return rest.visit(index, std::forward(visitor)); } } template bool visit(std::size_t index, V&& visitor) const { if (index == Index) { std::forward(visitor)(type_tag{}, index_tag{}, this); return true; } else { return rest.visit(index, std::forward(visitor)); } } empty_type empty; T value; storage_base...> rest; }; template union storage_base, type_index...> { constexpr storage_base() : empty{} {} template constexpr storage_base(type_tag, Args&&... args) : value(std::forward(args)...) {} template constexpr storage_base(index_tag, Args&&... args) : value(std::forward(args)...) {} template constexpr storage_base(type_tag, Args&&... args) : rest(type_tag{}, std::forward(args)...) {} template constexpr storage_base(index_tag, Args&&... args) : rest(index_tag{}, std::forward(args)...) {} // Trivial destructor. ~storage_base() = default; // Trival copy/move construction and assignment. constexpr storage_base(const storage_base&) = default; constexpr storage_base(storage_base&&) = default; constexpr storage_base& operator=(const storage_base&) = default; constexpr storage_base& operator=(storage_base&&) = default; constexpr void construct_at(std::size_t index, const storage_base& other) { if (index == Index) { new (&value) T{other.value}; } else { rest.construct_at(index, other.rest); } } constexpr void construct_at(std::size_t index, storage_base&& other) { if (index == Index) { new (&value) T{std::move(other.value)}; } else { rest.construct_at(index, std::move(other.rest)); } } constexpr void assign_at(std::size_t index, const storage_base& other) { if (index == Index) { value = other.value; } else { rest.assign_at(index, other.rest); } } constexpr void assign_at(std::size_t index, storage_base&& other) { if (index == Index) { value = std::move(other.value); } else { rest.assign_at(index, std::move(other.rest)); } } constexpr void swap_at(std::size_t index, storage_base& other) { if (index == Index) { using std::swap; swap(value, other.value); } else { rest.swap_at(index, other.rest); } } template constexpr std::size_t construct(type_tag, Args&&... args) { new (&value) T(std::forward(args)...); return Index; } template constexpr std::size_t construct(type_tag, Args&&... args) { return rest.construct(type_tag{}, std::forward(args)...); } template constexpr std::size_t construct(index_tag, Args&&... args) { new (&value) T(std::forward(args)...); return Index; } template constexpr std::size_t construct(index_tag, Args&&... args) { return rest.construct(index_tag{}, std::forward(args)...); } constexpr void reset(std::size_t) {} constexpr T& get(type_tag) { return value; } constexpr const T& get(type_tag) const { return value; } template constexpr U& get(type_tag) { return rest.get(type_tag{}); } template constexpr const U& get(type_tag) const { return rest.get(type_tag{}); } constexpr T& get(index_tag) { return value; } constexpr const T& get(index_tag) const { return value; } template constexpr auto& get(index_tag) { return rest.get(index_tag{}); } template constexpr const auto& get(index_tag) const { return rest.get(index_tag{}); } constexpr std::size_t index(type_tag) const { return Index; } template constexpr std::size_t index(type_tag) const { return rest.index(type_tag{}); } template constexpr bool visit(std::size_t index, V&& visitor) { if (index == Index) { std::forward(visitor)(type_tag{}, index_tag{}, this); return true; } else { return rest.visit(index, std::forward(visitor)); } } template constexpr bool visit(std::size_t index, V&& visitor) const { if (index == Index) { std::forward(visitor)(type_tag{}, index_tag{}, this); return true; } else { return rest.visit(index, std::forward(visitor)); } } empty_type empty; T value; storage_base...> rest; }; // Lazy-initialized union storage type that tracks the index of the active // variant. template class indexed_storage; template class indexed_storage...> { private: using base_type = storage_base..., type_index>; public: static constexpr bool nothrow_default_constructible = std::is_nothrow_default_constructible>::value; static constexpr bool nothrow_move_constructible = conjunction_v...>; static constexpr bool nothrow_move_assignable = conjunction_v...>; constexpr indexed_storage() = default; constexpr indexed_storage(trivial_init_t) : indexed_storage{} {} constexpr indexed_storage(default_init_t) : index_{0}, base_{index_tag<0>{}} {} // Only used by trivial copy/move types. constexpr indexed_storage(const indexed_storage& other) = default; constexpr indexed_storage& operator=(const indexed_storage& other) = default; constexpr indexed_storage(indexed_storage&& other) = default; constexpr indexed_storage& operator=(indexed_storage&& other) = default; template constexpr indexed_storage(type_tag, Args&&... args) : base_(type_tag{}, std::forward(args)...) { index_ = base_.index(type_tag{}); } template constexpr indexed_storage(index_tag, Args&&... args) : index_{Index}, base_(index_tag{}, std::forward(args)...) {} constexpr indexed_storage(maybe_init_t, const indexed_storage& other) : index_{other.index()}, base_{} { base_.construct_at(other.index(), other.base_); } constexpr indexed_storage(maybe_init_t, indexed_storage&& other) : index_{other.index()}, base_{} { base_.construct_at(other.index(), std::move(other.base_)); } ~indexed_storage() = default; constexpr index_type index() const { return index_; } constexpr bool is_empty() const { return index() == empty_index; } template constexpr bool has_value(type_tag) const { return index() == base_.index(type_tag{}); } template constexpr bool has_value(index_tag) const { return index() == Index; } template constexpr auto& get(type_tag) { return base_.get(type_tag{}); } template constexpr const auto& get(type_tag) const { return base_.get(type_tag{}); } template constexpr auto& get(index_tag) { return base_.get(index_tag{}); } template constexpr const auto& get(index_tag) const { return base_.get(index_tag{}); } template constexpr void construct(type_tag, Args&&... args) { index_ = base_.construct(type_tag{}, std::forward(args)...); } template constexpr void construct(index_tag, Args&&... args) { index_ = base_.construct(index_tag{}, std::forward(args)...); } constexpr void assign(const indexed_storage& other) { if (index() == other.index()) { base_.assign_at(index_, other.base_); } else { reset(); base_.construct_at(other.index_, other.base_); index_ = other.index_; } } constexpr void assign(indexed_storage&& other) { if (index() == other.index()) { base_.assign_at(index_, std::move(other.base_)); } else { reset(); base_.construct_at(other.index_, std::move(other.base_)); index_ = other.index_; } } template constexpr bool visit(V&& visitor) { return base_.visit(index_, std::forward(visitor)); } template constexpr bool visit(V&& visitor) const { return base_.visit(index_, std::forward(visitor)); } constexpr void swap(indexed_storage& other) { if (index() == other.index()) { // Swap directly when the variants are the same, including empty. base_.swap_at(index_, other.base_); } else { // Swap when the variants are different, including one being empty. // This approach avoids GCC -Wmaybe-uninitialized warnings by // initializing and accessing |temp| unconditionally within a // conditional scope. The alternative, using the maybe_init_t // constructor confuses GCC because it doesn't understand that the // index checks prevent uninitialized access. auto do_swap = [](indexed_storage& a, indexed_storage& b) { return a.base_.visit(a.index_, [&a, &b](auto, auto index_tag_v, auto* element) { indexed_storage temp{index_tag_v, std::move(element->value)}; a.reset(); a.base_.construct_at(b.index_, std::move(b.base_)); a.index_ = b.index_; b.reset(); b.base_.construct_at(temp.index_, std::move(temp.base_)); b.index_ = temp.index_; temp.reset(); }); }; // The visitor above returns false when the first argument is empty // and no action is taken. In that case, the other order is tried to // complete the half-empty swap. do_swap(*this, other) || do_swap(other, *this); } } // Destroys the active variant. Does nothing when already empty. constexpr void reset() { base_.reset(index_); index_ = empty_index; } private: index_type index_{empty_index}; base_type base_; }; // Internal variant storage type used by cpp17::optional and cpp17::variant. // Specializations of this type select trivial vs. non-trivial copy/move // construction, assignment operators, and destructor based on the storage class // of the types in Ts. template struct storage; template struct storage, type_index...> : indexed_storage...> { using base_type = indexed_storage...>; using base_type::base_type; constexpr storage() = default; }; template struct storage< storage_class, type_index...> : indexed_storage...> { using base_type = indexed_storage...>; using base_type::base_type; ~storage() = default; constexpr storage() = default; constexpr storage(const storage& other) : base_type{maybe_init_v, other} {} constexpr storage& operator=(const storage& other) { this->assign(other); return *this; } constexpr storage(storage&&) = default; constexpr storage& operator=(storage&&) = default; }; template struct storage< storage_class, type_index...> : indexed_storage...> { using base_type = indexed_storage...>; using base_type::base_type; ~storage() = default; constexpr storage() = default; constexpr storage(const storage&) = default; constexpr storage& operator=(const storage&) = default; constexpr storage(storage&& other) noexcept(base_type::nothrow_move_constructible) : base_type{maybe_init_v, std::move(other)} {} constexpr storage& operator=(storage&& other) noexcept(base_type::nothrow_move_assignable) { this->assign(std::move(other)); return *this; } }; template struct storage< storage_class, type_index...> : indexed_storage...> { using base_type = indexed_storage...>; using base_type::base_type; ~storage() = default; constexpr storage() = default; constexpr storage(const storage& other) : base_type{maybe_init_v, other} {} constexpr storage& operator=(const storage& other) { this->assign(other); return *this; } constexpr storage(storage&& other) noexcept(base_type::nothrow_move_constructible) : base_type{maybe_init_v, std::move(other)} {} constexpr storage& operator=(storage&& other) noexcept(base_type::nothrow_move_assignable) { this->assign(std::move(other)); return *this; } }; // Specialization for non-trivially movable/copyable types. Types with a non- // trivial destructor are always non-trivially movable/copyable. template struct storage, type_index...> : indexed_storage...> { using base_type = indexed_storage...>; using base_type::base_type; ~storage() { this->reset(); } constexpr storage() = default; constexpr storage(const storage& other) : base_type{maybe_init_v, other} {} constexpr storage& operator=(const storage& other) { this->assign(other); return *this; } constexpr storage(storage&& other) noexcept(base_type::nothrow_move_constructible) : base_type{maybe_init_v, std::move(other)} {} constexpr storage& operator=(storage&& other) noexcept(base_type::nothrow_move_assignable) { this->assign(std::move(other)); return *this; } }; template constexpr auto make_storage(std::index_sequence) { return storage, type_index...>{}; } template using storage_type = decltype(make_storage(std::index_sequence_for{})); } // namespace internal } // namespace cpp17 #endif // LIB_STDCOMPAT_INTERNAL_STORAGE_H_