blob: cd12f8843ab6d76dc712afc8515d6b514fc8493f (
plain) (
tree)
|
|
#ifndef UTILS_OPTION_H
#define UTILS_OPTION_H
#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 = std::enable_if_t<Cond, T>;
} // 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) noexcept : 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()));
}
}
constexpr option(const T& val) : m_has_value{true} {
new (&m_value) T(val);
}
constexpr option(T&& val) noexcept : m_has_value{true} {
new (&m_value) T(std::move(val));
}
// -- ASSIGNMENT -------------------------------------------------------------
constexpr option& operator=(const option& op) {
if (this == &op) {
return *this;
}
reset();
if (op.has_value()) {
new (&m_value) T(op.value());
m_has_value = true;
}
return *this;
}
constexpr option& operator=(option&& op) noexcept {
if (this == &op) {
return *this;
}
reset();
if (op.has_value()) {
new (&m_value) T(std::move(op.take()));
m_has_value = true;
}
return *this;
}
// -- DESTRUCTOR -------------------------------------------------------------
~option() {
reset();
}
// -- MODIFIER ---------------------------------------------------------------
template <typename... Params>
constexpr T& emplace(Params&&... params) {
static_assert(std::is_constructible<T, Params...>::value,
"T not constructible from 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
#endif
|