178 lines
4.0 KiB
C++
178 lines
4.0 KiB
C++
#pragma once
|
|
#include <luna/Badge.h>
|
|
#include <luna/Check.h>
|
|
#include <luna/Move.h>
|
|
#include <luna/PlacementNew.h>
|
|
|
|
template <typename T> class Result;
|
|
|
|
template <typename T> class Option
|
|
{
|
|
public:
|
|
Option(const T& value) : m_has_value(true)
|
|
{
|
|
m_storage.store_reference(value);
|
|
}
|
|
|
|
Option(T&& value) : m_has_value(true)
|
|
{
|
|
m_storage.store_moved_reference(move(value));
|
|
}
|
|
|
|
Option(const Option<T>& other) : m_has_value(other.m_has_value)
|
|
{
|
|
if (m_has_value) { m_storage.store_reference(other.m_storage.fetch_reference()); }
|
|
}
|
|
|
|
Option(Option<T>&& other) : m_has_value(other.m_has_value)
|
|
{
|
|
other.m_has_value = false;
|
|
if (m_has_value) { m_storage.store_moved_reference(move(other.m_storage.fetch_reference())); }
|
|
}
|
|
|
|
Option<T>& operator=(const Option<T>& other)
|
|
{
|
|
if (this == &other) return *this;
|
|
|
|
if (m_has_value) m_storage.destroy();
|
|
m_has_value = other.m_has_value;
|
|
|
|
if (m_has_value) { m_storage.store_reference(other.m_storage.fetch_reference()); }
|
|
|
|
return *this;
|
|
}
|
|
|
|
Option<T>& operator=(Option<T>&& other)
|
|
{
|
|
if (this == &other) return *this;
|
|
|
|
if (m_has_value) m_storage.destroy();
|
|
m_has_value = other.m_has_value;
|
|
other.m_has_value = false;
|
|
|
|
if (m_has_value) { m_storage.store_moved_reference(move(other.m_storage.fetch_reference())); }
|
|
|
|
return *this;
|
|
}
|
|
|
|
Option() : m_has_value(false)
|
|
{
|
|
}
|
|
|
|
bool has_value() const
|
|
{
|
|
return m_has_value;
|
|
}
|
|
|
|
T value(SourceLocation caller = SourceLocation::current()) const
|
|
{
|
|
expect_at(has_value(), caller, "Option::value called on an empty Option");
|
|
return m_storage.fetch_reference();
|
|
}
|
|
|
|
T unchecked_value(Badge<Result<T>>) const
|
|
{
|
|
return m_storage.fetch_reference();
|
|
}
|
|
|
|
T release_value(SourceLocation caller = SourceLocation::current())
|
|
{
|
|
expect_at(has_value(), caller, "Option::release_value called on an empty Option");
|
|
m_has_value = false;
|
|
return move(m_storage.fetch_reference());
|
|
}
|
|
|
|
T unchecked_release_value(Badge<Result<T>>)
|
|
{
|
|
m_has_value = false;
|
|
return move(m_storage.fetch_reference());
|
|
}
|
|
|
|
T value_or(const T& other) const
|
|
{
|
|
if (has_value()) return m_storage.fetch_reference();
|
|
return other;
|
|
}
|
|
|
|
bool try_set_value(T& ref) const
|
|
{
|
|
if (!has_value()) return false;
|
|
ref = m_storage.fetch_reference();
|
|
return true;
|
|
}
|
|
|
|
bool try_move_value(T& ref)
|
|
{
|
|
if (!has_value()) return false;
|
|
m_has_value = false;
|
|
ref = move(m_storage.fetch_reference());
|
|
return true;
|
|
}
|
|
|
|
~Option()
|
|
{
|
|
if (has_value()) m_storage.destroy();
|
|
}
|
|
|
|
// For compatibility with TRY()
|
|
struct ErrorHandle
|
|
{
|
|
private:
|
|
explicit ErrorHandle()
|
|
{
|
|
}
|
|
|
|
friend class Option<T>;
|
|
};
|
|
|
|
Option(const ErrorHandle&) : m_has_value(false)
|
|
{
|
|
}
|
|
|
|
ErrorHandle release_error(SourceLocation caller = SourceLocation::current())
|
|
{
|
|
expect_at(!has_value(), caller, "Option::release_error called on a non-empty Option");
|
|
return ErrorHandle {};
|
|
}
|
|
|
|
private:
|
|
struct Storage
|
|
{
|
|
alignas(T) u8 buffer[sizeof(T)];
|
|
|
|
T& fetch_reference()
|
|
{
|
|
return *__builtin_launder(reinterpret_cast<T*>(&buffer));
|
|
}
|
|
|
|
const T& fetch_reference() const
|
|
{
|
|
return *__builtin_launder(reinterpret_cast<const T*>(&buffer));
|
|
}
|
|
|
|
void store_reference(const T& ref)
|
|
{
|
|
new (buffer) T(ref);
|
|
}
|
|
|
|
void store_moved_reference(T&& ref)
|
|
{
|
|
new (buffer) T(move(ref));
|
|
}
|
|
|
|
void destroy()
|
|
{
|
|
fetch_reference().~T();
|
|
}
|
|
};
|
|
Storage m_storage;
|
|
bool m_has_value { false };
|
|
};
|
|
|
|
template <typename T> inline Option<T*> nonnull_or_empty_option(T* ptr)
|
|
{
|
|
if (ptr == nullptr) return {};
|
|
else
|
|
return ptr;
|
|
}
|