Luna/libluna/include/luna/Option.h

178 lines
4.0 KiB
C
Raw Normal View History

#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()
{
}
2023-01-07 19:53:23 +00:00
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");
2022-12-21 19:22:44 +00:00
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;
2022-12-21 19:22:44 +00:00
bool m_has_value { false };
};
template <typename T> inline Option<T*> nonnull_or_empty_option(T* ptr)
{
if (ptr == nullptr) return {};
else
return ptr;
2023-01-02 12:07:29 +00:00
}