Skip to content

Instantly share code, notes, and snippets.

@jeremy-rifkin
Created September 16, 2023 18:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeremy-rifkin/a0460ed16effab44241dedcd8b6f3987 to your computer and use it in GitHub Desktop.
Save jeremy-rifkin/a0460ed16effab44241dedcd8b6f3987 to your computer and use it in GitHub Desktop.
C++11 optional implementation
#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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment