|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include <memory>
|
|
|
|
#include <string_view>
|
|
|
|
#include <utility>
|
|
|
|
#include <array>
|
|
|
|
|
|
|
|
#include "exopt.h"
|
|
|
|
|
|
|
|
#include "optional.h"
|
|
|
|
|
|
|
|
#define _EO_CONSTANT_VALUE(X) ([]() { \
|
|
|
|
struct { \
|
|
|
|
typedef decltype(X) comptime_constant_t; \
|
|
|
|
constexpr static inline auto VALUE = (X); \
|
|
|
|
consteval operator comptime_constant_t() const noexcept { return (X); } \
|
|
|
|
} inner; \
|
|
|
|
return inner; \
|
|
|
|
})()
|
|
|
|
|
|
|
|
namespace exopt { namespace util [[gnu::visibility("internal")]] {
|
|
|
|
template<typename T, typename U>
|
|
|
|
concept comptime = std::convertible_to<T, U> and requires{
|
|
|
|
typename T::comptime_constant_t;
|
|
|
|
};
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
concept is_complete = requires{ { sizeof(T) == sizeof(T) }; };
|
|
|
|
|
|
|
|
constexpr auto comptime_value(auto value) noexcept {
|
|
|
|
using U = decltype(value);
|
|
|
|
struct inner final {
|
|
|
|
typedef U comptime_constant_t;
|
|
|
|
consteval operator comptime_constant_t() const noexcept { return std::move(value); }
|
|
|
|
U value;
|
|
|
|
|
|
|
|
constexpr inner(std::add_rvalue_reference_t<U> m) noexcept : value(std::move(m)) {}
|
|
|
|
constexpr ~inner() = default;
|
|
|
|
};
|
|
|
|
return inner{ std::move(value) };
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T, auto value> requires(comptime<decltype(value), T>)
|
|
|
|
consteval T comptime_eval() noexcept {
|
|
|
|
if constexpr(requires {
|
|
|
|
{ decltype(value)::VALUE } -> std::convertible_to<decltype(value)::comptime_constant_t>;
|
|
|
|
}) {
|
|
|
|
return decltype(value)::VALUE;
|
|
|
|
} else return value;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T, auto value> requires(comptime<decltype(value), T>)
|
|
|
|
constexpr inline T comptime_eval_v = comptime_eval<T, value>();
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
consteval T comptime_eval(comptime<T> auto value) noexcept {
|
|
|
|
return comptime_eval<T, value>();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static_assert(comptime_eval(comptime_value(5.0)) == 5.0, "Bad comptime_eval() consteval");
|
|
|
|
static_assert(comptime_eval(_EO_CONSTANT_VALUE(3.f)) == 3.f, "Bad comptime_eval() macro");
|
|
|
|
static_assert(std::is_same_v<decltype(comptime_eval(comptime_value(4.f))), float>, "Bad comptime_eval() return type consteval");
|
|
|
|
static_assert(std::is_same_v<decltype(comptime_eval(_EO_CONSTANT_VALUE(4.0))), double>, "Bad comptime_eval() return type macro");
|
|
|
|
static_assert(comptime_value(std::string_view{"hello"}.size()) == 5, "Bad comptime_value()");
|
|
|
|
static_assert(_EO_CONSTANT_VALUE(std::string_view{"hello world"}) == std::string_view{"hello world"}, "Bad CONSTANT_VALUE()");
|
|
|
|
|
|
|
|
|
|
|
|
template<typename T, size_t N, typename Array= std::array<T, N>, typename _I = std::make_index_sequence<N>>
|
|
|
|
constexpr auto array_literal(const T (&a)[N]) noexcept
|
|
|
|
-> Array
|
|
|
|
{
|
|
|
|
constexpr decltype(auto) _array_create = []<size_t... I>(auto const& a, std::index_sequence<I...>) noexcept
|
|
|
|
{
|
|
|
|
return Array { a[I]... };
|
|
|
|
};
|
|
|
|
return _array_create(std::move(a), _I{});
|
|
|
|
}
|
|
|
|
template<typename T, size_t N, typename Array= std::array<T, N>, typename _I = std::make_index_sequence<N>>
|
|
|
|
constexpr auto array_literal(T (&&a)[N]) noexcept
|
|
|
|
-> Array
|
|
|
|
{
|
|
|
|
constexpr decltype(auto) _array_create = []<size_t... I>(auto&& a, std::index_sequence<I...>) noexcept
|
|
|
|
{
|
|
|
|
return Array { std::move(a[I])... };
|
|
|
|
};
|
|
|
|
return _array_create(std::move(a), _I{});
|
|
|
|
}
|
|
|
|
|
|
|
|
struct [[gnu::visibility("internal")]] CTInternalFatalError {
|
|
|
|
constexpr CTInternalFatalError() noexcept = default;
|
|
|
|
constexpr CTInternalFatalError(CTInternalFatalError const&) noexcept = default;
|
|
|
|
constexpr CTInternalFatalError(CTInternalFatalError &&) noexcept = default;
|
|
|
|
constexpr CTInternalFatalError& operator=(CTInternalFatalError const&) noexcept = default;
|
|
|
|
constexpr CTInternalFatalError& operator=(CTInternalFatalError&&) noexcept = default;
|
|
|
|
constexpr virtual ~CTInternalFatalError() noexcept = default;
|
|
|
|
|
|
|
|
constexpr operator std::string_view() const noexcept { return message(); }
|
|
|
|
|
|
|
|
consteval virtual std::string_view message() const noexcept =0;
|
|
|
|
};
|
|
|
|
|
|
|
|
[[noreturn, gnu::noinline, gnu::cold]]
|
|
|
|
void throw_runtime(std::string_view&&);
|
|
|
|
|
|
|
|
[[noreturn]]//, gnu::noinline, gnu::cold]]
|
|
|
|
constexpr void throw_runtime(std::convertible_to<std::string_view> auto&& msg) {
|
|
|
|
std::string_view view{msg};
|
|
|
|
if consteval {
|
|
|
|
using CTE = CTInternalFatalError;
|
|
|
|
struct CTIFRuntimeError : public CTE {
|
|
|
|
using CTE::CTE;
|
|
|
|
consteval CTIFRuntimeError(std::string_view&& view) noexcept
|
|
|
|
: CTE(), m_message(std::move(view)) {}
|
|
|
|
consteval std::string_view message() const noexcept override { return m_message; }
|
|
|
|
constexpr virtual ~CTIFRuntimeError() noexcept {}
|
|
|
|
private:
|
|
|
|
std::string_view m_message;
|
|
|
|
};
|
|
|
|
throw CTIFRuntimeError(std::move(view));
|
|
|
|
} else {
|
|
|
|
throw_runtime(std::move(view));
|
|
|
|
}
|
|
|
|
__builtin_unreachable();
|
|
|
|
}
|
|
|
|
// Returns an anonymous union with inavtive field: `T value` and active field `assume init` (no unique address).
|
|
|
|
template<typename T> requires(!std::is_reference_v<T>)
|
|
|
|
constexpr auto uninit() noexcept {
|
|
|
|
union UI {
|
|
|
|
struct assume{};
|
|
|
|
T value;
|
|
|
|
[[no_unique_address]] assume init{};
|
|
|
|
};
|
|
|
|
return UI{};
|
|
|
|
}
|
|
|
|
template<typename T> requires(!std::is_reference_v<T>)
|
|
|
|
struct alignas(T) maybe_uninit {
|
|
|
|
using uninit = std::array<unsigned char, sizeof(T)>;
|
|
|
|
|
|
|
|
constexpr maybe_uninit() noexcept {}
|
|
|
|
|
|
|
|
|
|
|
|
constexpr maybe_uninit(const maybe_uninit& c) noexcept
|
|
|
|
: m_uninit(c.m_uninit) {}
|
|
|
|
constexpr maybe_uninit(maybe_uninit&& m) noexcept
|
|
|
|
: m_uninit(std::move(m.m_uninit)) {}
|
|
|
|
|
|
|
|
constexpr maybe_uninit& operator=(maybe_uninit const& c) noexcept
|
|
|
|
{ m_uninit = c.m_uninit; return *this; }
|
|
|
|
|
|
|
|
constexpr maybe_uninit& operator=(maybe_uninit&& c) noexcept
|
|
|
|
{ m_uninit = std::move(c.m_uninit); return *this; }
|
|
|
|
|
|
|
|
constexpr T& operator=(T&& value) noexcept {
|
|
|
|
m_init = std::move(value); return *this;
|
|
|
|
}
|
|
|
|
constexpr T& operator=(T const& copy) noexcept {
|
|
|
|
m_init = maybe_uninit{copy}; return *this; //XXX: These assignment operators may not be a good idea, we can't guarantee which active destriminant it is.
|
|
|
|
}
|
|
|
|
|
|
|
|
constexpr T& assume_init() & noexcept = delete;
|
|
|
|
constexpr T&& assume_init() && noexcept { return std::move(m_init); }
|
|
|
|
constexpr T const& assume_init() const& noexcept { return m_init; }
|
|
|
|
constexpr T const&& assume_init() const&& noexcept { return m_init; }
|
|
|
|
|
|
|
|
constexpr uninit& data() &noexcept { return m_uninit; }
|
|
|
|
constexpr uninit&& data() &&noexcept { return std::move(m_uninit); }
|
|
|
|
constexpr uninit const& data() const&noexcept { return m_uninit; }
|
|
|
|
constexpr uninit const&& data() const&& noexcept { return std::move(m_uninit); }
|
|
|
|
|
|
|
|
constexpr T* get() noexcept { return std::addressof(m_init); }
|
|
|
|
constexpr const T* get() const noexcept { return std::addressof(m_init); }
|
|
|
|
|
|
|
|
//TODO: accessors, `assume_init_*()`, etc.
|
|
|
|
|
|
|
|
constexpr ~maybe_uninit() noexcept {}
|
|
|
|
|
|
|
|
constexpr void assume_init_drop() noexcept(std::is_nothrow_destructible_v<T>)
|
|
|
|
{ m_init.~T(); m_uninit = {}; }
|
|
|
|
|
|
|
|
[[nodiscard]]
|
|
|
|
constexpr T assume_init_take() noexcept(std::is_nothrow_move_constructible_v<T>)
|
|
|
|
{
|
|
|
|
auto v = std::move(m_init);
|
|
|
|
assume_init_drop();
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
|
|
|
constexpr maybe_uninit<T> init(T&& value) noexcept(std::is_nothrow_move_constructible_v<T>)
|
|
|
|
{ return maybe_uninit<T>{std::move(value), EXPLICIT_INIT_TAG<EXPLICIT_INIT_MOVE>{}}; }
|
|
|
|
|
|
|
|
template<typename... Args> requires(std::is_constructible_v<T, Args...>)
|
|
|
|
constexpr maybe_uninit<T> init_in_place(Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
|
|
|
|
{ return maybe_uninit<T>{EXPLICIT_INIT_TAG<EXPLICIT_INIT_IN_PLACE>{}, std::forward<Args>(args)...}; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
enum EXPLICIT_INIT_TYPE {
|
|
|
|
EXPLICIT_INIT_IN_PLACE,
|
|
|
|
EXPLICIT_INIT_MOVE,
|
|
|
|
};
|
|
|
|
template<EXPLICIT_INIT_TYPE>
|
|
|
|
struct EXPLICIT_INIT_TAG {};
|
|
|
|
constexpr maybe_uninit(T&& value, EXPLICIT_INIT_TAG<EXPLICIT_INIT_MOVE>) noexcept(std::is_nothrow_move_constructible_v<T>)
|
|
|
|
: m_init{std::move(value)} {}
|
|
|
|
template<typename... Args> requires(std::is_constructible_v<T, Args...>)
|
|
|
|
constexpr maybe_uninit(EXPLICIT_INIT_TAG<EXPLICIT_INIT_IN_PLACE> _a, Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
|
|
|
|
: m_init{std::forward<Args>(args)...} { (void) _a; }
|
|
|
|
|
|
|
|
union {
|
|
|
|
T m_init;
|
|
|
|
[[no_unique_address]] alignas(T) uninit m_uninit{};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
constexpr std::string_view ui_test() noexcept {
|
|
|
|
auto ui = uninit<std::string_view>();
|
|
|
|
|
|
|
|
maybe_uninit<std::string_view> mi;
|
|
|
|
|
|
|
|
ui.init.~assume();
|
|
|
|
ui.value = std::string_view{"Hello"};
|
|
|
|
return std::move(ui.value);
|
|
|
|
}
|
|
|
|
static_assert(ui_test()[0] == 'H', "Bad uninit<T>()");
|
|
|
|
}
|
|
|
|
} }
|