#ifndef UTILS_OPTION_H #define UTILS_OPTION_H #include #include // placement new #include #include // move namespace option { namespace impl { /// Definition of std::is_trivially_destructible_v for older c++ std versions. template constexpr bool is_trivially_destructible_v = std::is_trivially_destructible::value; /// Definition of std::enable_if_t for older c++ std versions. template using enable_if_t = typename std::enable_if::type; } // namespace impl /// The NONE type. struct none {}; /// The OPTION type. template struct option { static_assert(!std::is_reference::value, "T must not be a reference type"); // -- CONSTRUCTOR - NONE ----------------------------------------------------- constexpr option() = default; constexpr option(none) : option() {} // -- CONSTRUCTOR - VALUE ---------------------------------------------------- constexpr option(const option& op) : m_has_value{op.has_value()} { if (op.has_value()) { // Copy construct from inner VALUE of OP. new (&m_value) T(op.value()); } } constexpr option(option&& op) noexcept : m_has_value{op.has_value()} { if (op.m_has_value) { // Move construct from inner VALUE of OP. new (&m_value) T(std::move(op.take())); } } template constexpr option(T&& val) : m_has_value{true} { new (&m_value) T(std::move(val)); } // -- DESTRUCTOR ------------------------------------------------------------- ~option() { reset(); } // -- MODIFIER --------------------------------------------------------------- template constexpr T& emplace(Params&&... params) { reset(); new (&m_value) T(std::forward(params)...); m_has_value = true; return value(); } // -- OPERATOR --------------------------------------------------------------- /// Conversion to BOOL, true iff option holds a VALUE. /// /// Marked as explicit to disable implicit conversion of option objects to /// bool in the following case: /// /// if (opt1 == opt2) { /// ... /// } constexpr explicit operator bool() const { return has_value(); } // -- ACCESSOR --------------------------------------------------------------- constexpr bool has_value() const { return m_has_value; } constexpr T& value() & { assert(m_has_value); // Launder pointer, this informs the compiler that certain optimizations are // not applicable such as CONSTPROP. This is required because every use // of PLACEMENT new on M_VALUE starts a new LIFETIME and returning a // pointer based on the LIFETIME of M_VALUE is UB [1][2]. // // Notes: // * Obtaining a pointer to an object created by placement new from a // pointer to an object providing storage for that object [1]. // * See example in [1] and [2]. // // [1]: https://en.cppreference.com/w/cpp/utility/launder // [2]: https://en.cppreference.com/w/cpp/types/aligned_storage return *__builtin_launder(reinterpret_cast(m_value)); } constexpr const T& value() const& { assert(m_has_value); // Launder, see other value(). return *__builtin_launder(reinterpret_cast(m_value)); } constexpr T value() && { return take(); } constexpr T take() { assert(m_has_value); T val = std::move(value()); reset(); return val; } // -- INTERNAL --------------------------------------------------------------- private: template > = true> constexpr void reset() { if (m_has_value) { value().~T(); m_has_value = false; } } template > = true> constexpr void reset() { m_has_value = false; } alignas(T) char m_value[sizeof(T)]; bool m_has_value{false}; }; } // namespace option #endif