#ifndef AL_OPTIONAL_H #define AL_OPTIONAL_H #include #include #include #include "almalloc.h" namespace al { struct nullopt_t { }; struct in_place_t { }; constexpr nullopt_t nullopt{}; constexpr in_place_t in_place{}; #define NOEXCEPT_AS(...) noexcept(noexcept(__VA_ARGS__)) namespace detail_ { /* Base storage struct for an optional. Defines a trivial destructor, for types * that can be trivially destructed. */ template::value> struct optstore_base { bool mHasValue{false}; union { char mDummy; T mValue; }; constexpr optstore_base() noexcept { } template constexpr explicit optstore_base(in_place_t, Args&& ...args) noexcept(std::is_nothrow_constructible::value) : mHasValue{true}, mValue{std::forward(args)...} { } ~optstore_base() = default; }; /* Specialization needing a non-trivial destructor. */ template struct optstore_base { bool mHasValue{false}; union { char mDummy; T mValue; }; constexpr optstore_base() noexcept { } template constexpr explicit optstore_base(in_place_t, Args&& ...args) noexcept(std::is_nothrow_constructible::value) : mHasValue{true}, mValue{std::forward(args)...} { } ~optstore_base() { if(mHasValue) al::destroy_at(std::addressof(mValue)); } }; /* Next level of storage, which defines helpers to construct and destruct the * stored object. */ template struct optstore_helper : public optstore_base { using optstore_base::optstore_base; template constexpr void construct(Args&& ...args) noexcept(std::is_nothrow_constructible::value) { al::construct_at(std::addressof(this->mValue), std::forward(args)...); this->mHasValue = true; } constexpr void reset() noexcept { if(this->mHasValue) al::destroy_at(std::addressof(this->mValue)); this->mHasValue = false; } constexpr void assign(const optstore_helper &rhs) noexcept(std::is_nothrow_copy_constructible::value && std::is_nothrow_copy_assignable::value) { if(!rhs.mHasValue) this->reset(); else if(this->mHasValue) this->mValue = rhs.mValue; else this->construct(rhs.mValue); } constexpr void assign(optstore_helper&& rhs) noexcept(std::is_nothrow_move_constructible::value && std::is_nothrow_move_assignable::value) { if(!rhs.mHasValue) this->reset(); else if(this->mHasValue) this->mValue = std::move(rhs.mValue); else this->construct(std::move(rhs.mValue)); } }; /* Define copy and move constructors and assignment operators, which may or may * not be trivial. */ template::value, bool trivial_move = std::is_trivially_move_constructible::value, /* Trivial assignment is dependent on trivial construction+destruction. */ bool = trivial_copy && std::is_trivially_copy_assignable::value && std::is_trivially_destructible::value, bool = trivial_move && std::is_trivially_move_assignable::value && std::is_trivially_destructible::value> struct optional_storage; /* Some versions of GCC have issues with 'this' in the following noexcept(...) * statements, so this macro is a workaround. */ #define _this std::declval() /* Completely trivial. */ template struct optional_storage : public optstore_helper { using optstore_helper::optstore_helper; constexpr optional_storage() noexcept = default; constexpr optional_storage(const optional_storage&) = default; constexpr optional_storage(optional_storage&&) = default; constexpr optional_storage& operator=(const optional_storage&) = default; constexpr optional_storage& operator=(optional_storage&&) = default; }; /* Non-trivial move assignment. */ template struct optional_storage : public optstore_helper { using optstore_helper::optstore_helper; constexpr optional_storage() noexcept = default; constexpr optional_storage(const optional_storage&) = default; constexpr optional_storage(optional_storage&&) = default; constexpr optional_storage& operator=(const optional_storage&) = default; constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) { this->assign(std::move(rhs)); return *this; } }; /* Non-trivial move construction. */ template struct optional_storage : public optstore_helper { using optstore_helper::optstore_helper; constexpr optional_storage() noexcept = default; constexpr optional_storage(const optional_storage&) = default; constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } constexpr optional_storage& operator=(const optional_storage&) = default; constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) { this->assign(std::move(rhs)); return *this; } }; /* Non-trivial copy assignment. */ template struct optional_storage : public optstore_helper { using optstore_helper::optstore_helper; constexpr optional_storage() noexcept = default; constexpr optional_storage(const optional_storage&) = default; constexpr optional_storage(optional_storage&&) = default; constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) { this->assign(rhs); return *this; } constexpr optional_storage& operator=(optional_storage&&) = default; }; /* Non-trivial copy construction. */ template struct optional_storage : public optstore_helper { using optstore_helper::optstore_helper; constexpr optional_storage() noexcept = default; constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) { if(rhs.mHasValue) this->construct(rhs.mValue); } constexpr optional_storage(optional_storage&&) = default; constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) { this->assign(rhs); return *this; } constexpr optional_storage& operator=(optional_storage&&) = default; }; /* Non-trivial assignment. */ template struct optional_storage : public optstore_helper { using optstore_helper::optstore_helper; constexpr optional_storage() noexcept = default; constexpr optional_storage(const optional_storage&) = default; constexpr optional_storage(optional_storage&&) = default; constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) { this->assign(rhs); return *this; } constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) { this->assign(std::move(rhs)); return *this; } }; /* Non-trivial assignment, non-trivial move construction. */ template struct optional_storage : public optstore_helper { using optstore_helper::optstore_helper; constexpr optional_storage() noexcept = default; constexpr optional_storage(const optional_storage&) = default; constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) { this->assign(rhs); return *this; } constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) { this->assign(std::move(rhs)); return *this; } }; /* Non-trivial assignment, non-trivial copy construction. */ template struct optional_storage : public optstore_helper { using optstore_helper::optstore_helper; constexpr optional_storage() noexcept = default; constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) { if(rhs.mHasValue) this->construct(rhs.mValue); } constexpr optional_storage(optional_storage&&) = default; constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) { this->assign(rhs); return *this; } constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) { this->assign(std::move(rhs)); return *this; } }; /* Completely non-trivial. */ template struct optional_storage : public optstore_helper { using optstore_helper::optstore_helper; constexpr optional_storage() noexcept = default; constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) { if(rhs.mHasValue) this->construct(rhs.mValue); } constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) { this->assign(rhs); return *this; } constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) { this->assign(std::move(rhs)); return *this; } }; #undef _this } // namespace detail_ #define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true template class optional { using storage_t = detail_::optional_storage; storage_t mStore{}; public: using value_type = T; constexpr optional() = default; constexpr optional(const optional&) = default; constexpr optional(optional&&) = default; constexpr optional(nullopt_t) noexcept { } template constexpr explicit optional(in_place_t, Args&& ...args) NOEXCEPT_AS(storage_t{al::in_place, std::forward(args)...}) : mStore{al::in_place, std::forward(args)...} { } template::value && !std::is_same, al::in_place_t>::value && !std::is_same, optional>::value && std::is_convertible::value)> constexpr optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward(rhs)}) : mStore{al::in_place, std::forward(rhs)} { } template::value && !std::is_same, al::in_place_t>::value && !std::is_same, optional>::value && !std::is_convertible::value)> constexpr explicit optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward(rhs)}) : mStore{al::in_place, std::forward(rhs)} { } ~optional() = default; constexpr optional& operator=(const optional&) = default; constexpr optional& operator=(optional&&) = default; constexpr optional& operator=(nullopt_t) noexcept { mStore.reset(); return *this; } template constexpr std::enable_if_t::value && std::is_assignable::value && !std::is_same, optional>::value && (!std::is_same, T>::value || !std::is_scalar::value), optional&> operator=(U&& rhs) { if(mStore.mHasValue) mStore.mValue = std::forward(rhs); else mStore.construct(std::forward(rhs)); return *this; } constexpr const T* operator->() const { return std::addressof(mStore.mValue); } constexpr T* operator->() { return std::addressof(mStore.mValue); } constexpr const T& operator*() const& { return mStore.mValue; } constexpr T& operator*() & { return mStore.mValue; } constexpr const T&& operator*() const&& { return std::move(mStore.mValue); } constexpr T&& operator*() && { return std::move(mStore.mValue); } constexpr explicit operator bool() const noexcept { return mStore.mHasValue; } constexpr bool has_value() const noexcept { return mStore.mHasValue; } constexpr T& value() & { return mStore.mValue; } constexpr const T& value() const& { return mStore.mValue; } constexpr T&& value() && { return std::move(mStore.mValue); } constexpr const T&& value() const&& { return std::move(mStore.mValue); } template constexpr T value_or(U&& defval) const& { return bool{*this} ? **this : static_cast(std::forward(defval)); } template constexpr T value_or(U&& defval) && { return bool{*this} ? std::move(**this) : static_cast(std::forward(defval)); } template constexpr T& emplace(Args&& ...args) { mStore.reset(); mStore.construct(std::forward(args)...); return mStore.mValue; } template constexpr std::enable_if_t&, Args&&...>::value, T&> emplace(std::initializer_list il, Args&& ...args) { mStore.reset(); mStore.construct(il, std::forward(args)...); return mStore.mValue; } constexpr void reset() noexcept { mStore.reset(); } }; template constexpr optional> make_optional(T&& arg) { return optional>{in_place, std::forward(arg)}; } template constexpr optional make_optional(Args&& ...args) { return optional{in_place, std::forward(args)...}; } template constexpr optional make_optional(std::initializer_list il, Args&& ...args) { return optional{in_place, il, std::forward(args)...}; } #undef REQUIRES #undef NOEXCEPT_AS } // namespace al #endif /* AL_OPTIONAL_H */