aboutsummaryrefslogblamecommitdiff
path: root/option.h
blob: ab7a72e4288ada7839df4fe4937d184e52ae6e39 (plain) (tree)



















                                                                              
                    

















                                                                                
                                                                        
















































                                                                                












                                                                                



                                     

                                                                   


































                                                                                
#include <cassert>
#include <new>  // placement new
#include <type_traits>
#include <utility>  // move

namespace option {
namespace impl {
/// Definition of std::is_trivially_destructible_v for older c++ std versions.
template <typename T>
constexpr bool is_trivially_destructible_v =
    std::is_trivially_destructible<T>::value;

/// Definition of std::enable_if_t for older c++ std versions.
template <bool Cond, typename T = bool>
using enable_if_t = typename std::enable_if<Cond, T>::type;
}  // namespace impl

/// The NONE type.
struct none {};

/// The OPTION type.
template <typename T>
struct option {
  static_assert(!std::is_reference<T>::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 <typename U = T>
  constexpr option(T&& val) : m_has_value{true} {
    new (m_value) T(std::move(val));
  }

  // -- DESTRUCTOR -------------------------------------------------------------

  ~option() {
    reset();
  }

  // -- MODIFIER ---------------------------------------------------------------

  template <typename... Params>
  constexpr T& emplace(Params&&... params) {
    reset();
    new (m_value) T(std::forward<Params>(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<T*>(m_value));
  }

  constexpr const T& value() const& {
    assert(m_has_value);
    // Launder, see other value().
    return *__builtin_launder(reinterpret_cast<const T*>(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 <typename U = T,
            impl::enable_if_t<!impl::is_trivially_destructible_v<U>> = true>
  constexpr void reset() {
    if (m_has_value) {
      value().~T();
      m_has_value = false;
    }
  }

  template <typename U = T,
            impl::enable_if_t<impl::is_trivially_destructible_v<U>> = true>
  constexpr void reset() {
    m_has_value = false;
  }

  alignas(T) char m_value[sizeof(T)];
  bool m_has_value{false};
};
}  // namespace option