Skip to content

Instantly share code, notes, and snippets.

@jeremy-rifkin
Created September 16, 2023 18:31
Show Gist options
  • Select an option

  • Save jeremy-rifkin/a0460ed16effab44241dedcd8b6f3987 to your computer and use it in GitHub Desktop.

Select an option

Save jeremy-rifkin/a0460ed16effab44241dedcd8b6f3987 to your computer and use it in GitHub Desktop.

Revisions

  1. jeremy-rifkin created this gist Sep 16, 2023.
    158 changes: 158 additions & 0 deletions optional.hpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,158 @@
    #ifndef OPTIONAL_HPP
    #define OPTIONAL_HPP

    #include <memory>
    #include <new>
    #include <stdexcept>
    #include <type_traits>
    #include <utility>

    struct nullopt_t {};

    static constexpr nullopt_t nullopt;

    template<typename T, typename std::enable_if<!std::is_same<typename std::decay<T>::type, void>::value, int>::type = 0>
    class optional {
    bool holds_value = false;

    union {
    T uvalue;
    };

    public:
    // clang-tidy false positive
    // NOLINTNEXTLINE(modernize-use-equals-default)
    optional() noexcept {}

    optional(nullopt_t) noexcept {}

    ~optional() {
    reset();
    }

    optional(const optional& other) : holds_value(other.holds_value) {
    if(holds_value) {
    new (static_cast<void*>(std::addressof(uvalue))) T(other.uvalue);
    }
    }

    optional(optional&& other) noexcept(std::is_nothrow_move_constructible<T>::value) : holds_value(other.holds_value) {
    if(holds_value) {
    new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
    }
    }

    optional& operator=(const optional& other) {
    optional copy(other);
    swap(*this, copy);
    return *this;
    }

    optional& operator=(optional&& other) noexcept(
    std::is_nothrow_move_assignable<T>::value && std::is_nothrow_move_constructible<T>::value
    ) {
    reset();
    if(other.holds_value) {
    new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
    holds_value = true;
    }
    return *this;
    }

    template<
    typename U = T,
    typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
    >
    // clang-tidy false positive
    // NOLINTNEXTLINE(bugprone-forwarding-reference-overload)
    optional(U&& value) : holds_value(true) {
    new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(value));
    }

    template<
    typename U = T,
    typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
    >
    optional& operator=(U&& value) {
    if(holds_value) {
    uvalue = std::forward<U>(value);
    } else {
    new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(value));
    holds_value = true;
    }
    return *this;
    }

    optional& operator=(nullopt_t) noexcept {
    reset();
    return *this;
    }

    void swap(optional& other) {
    if(holds_value && other.holds_value) {
    std::swap(uvalue, other.uvalue);
    } else if(holds_value && !other.holds_value) {
    new (&other.uvalue) T(std::move(uvalue));
    uvalue.~T();
    } else if(!holds_value && other.holds_value) {
    new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
    other.uvalue.~T();
    }
    std::swap(holds_value, other.holds_value);
    }

    bool has_value() const {
    return holds_value;
    }

    operator bool() const {
    return holds_value;
    }

    void reset() {
    if(holds_value) {
    uvalue.~T();
    }
    holds_value = false;
    }

    T& unwrap() & {
    if(!holds_value) {
    throw std::runtime_error{"Optional does not contain a value"};
    }
    return uvalue;
    }

    const T& unwrap() const & {
    if(!holds_value) {
    throw std::runtime_error{"Optional does not contain a value"};
    }
    return uvalue;
    }

    T&& unwrap() && {
    if(!holds_value) {
    throw std::runtime_error{"Optional does not contain a value"};
    }
    return std::move(uvalue);
    }

    const T&& unwrap() const && {
    if(!holds_value) {
    throw std::runtime_error{"Optional does not contain a value"};
    }
    return std::move(uvalue);
    }

    template<typename U>
    T value_or(U&& default_value) const & {
    return holds_value ? uvalue : static_cast<T>(std::forward<U>(default_value));
    }

    template<typename U>
    T value_or(U&& default_value) && {
    return holds_value ? std::move(uvalue) : static_cast<T>(std::forward<U>(default_value));
    }
    };

    #endif