I'm preparing for entry-level C++ developer interview and decided to implement some of std:: members. Here's my implementation of std::optional. I would be grateful for any feedback to help me improve my implementation.
#include <memory>
#include <type_traits>
#include <utility>
#include <cassert>
template<typename T>
struct optional {
optional() noexcept : _engaged(false) {}
optional(T&& value) noexcept : _value(std::move(value)), _engaged(true) {}
optional(optional&& rhs) noexcept : _engaged(std::exchange(rhs._engaged, false)) {
if (_engaged) {
std::construct_at(std::addressof(_value), std::move(rhs._value));
}
}
optional& operator=(optional&& rhs) noexcept {
if (this != &rhs) {
if (rhs._engaged) {
if (_engaged) {
_value = std::move(rhs._value);
} else {
std::construct_at(std::addressof(_value), std::move(rhs._value));
_engaged = true;
}
} else {
reset();
}
rhs.reset();
}
return *this;
}
optional(const optional& rhs) noexcept : _engaged(rhs._engaged) {
if (_engaged) {
std::construct_at(std::addressof(_value), rhs._value);
}
}
optional& operator=(const optional& rhs) noexcept {
if (this != &rhs) {
if (rhs._engaged) {
if (_engaged) {
_value = rhs._value;
} else {
std::construct_at(std::addressof(_value), rhs._value);
_engaged = true;
}
} else {
reset();
}
}
return *this;
}
~optional() noexcept { reset(); }
[[nodiscard]] T& operator*() noexcept {
assert(_engaged && "Attempting to access a disengaged optional!");
return _value;
}
[[nodiscard]] const T& operator*() const noexcept {
assert(_engaged && "Attempting to access a disengaged optional!");
return _value;
}
[[nodiscard]] T* operator->() noexcept {
assert(_engaged && "Attempting to dereference a disengaged optional!");
return std::addressof(_value);
}
[[nodiscard]] const T* operator->() const noexcept {
assert(_engaged && "Attempting to dereference a disengaged optional!");
return std::addressof(_value);
}
operator bool() const noexcept {
return _engaged;
}
bool has_value() const noexcept {
return _engaged;
}
template<typename... Args>
T& emplace(Args... args) {
reset();
std::construct_at(std::addressof(_value), std::forward<Args>(args)...);
_engaged = true;
return _value;
}
template<typename U>
[[nodiscard]] T value_or(U&& other_value) const & noexcept
requires(std::is_copy_constructible_v<T> && std::is_convertible_v<U&&, T>) {
return _engaged ? _value : static_cast<T>(std::forward<U>(other_value));
}
template<typename U>
[[nodiscard]] T value_or(U&& other_value) const && noexcept
requires(std::is_move_constructible_v<T> && std::is_convertible_v<U&&, T>) {
return _engaged ? std::move(_value) : static_cast<T>(std::forward<U>(other_value));
}
T& value() & noexcept {
return _value;
}
T&& value() && noexcept {
return std::move(_value);
}
void reset() noexcept {
if (_engaged) {
if constexpr (!std::is_trivially_destructible_v<T>) {
_value.~T();
}
_engaged = false;
}
}
private:
union {
std::byte _null_state;
T _value;
};
bool _engaged;
};