You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libexopt/include/util.hh

435 lines
18 KiB

#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, typename U>
concept shares_layout = sizeof(T) == sizeof(U) && alignof(T) == alignof(U);
template<typename T, typename U>
concept exact_type = std::is_same_v<T, U>;
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{});
}
template<size_t N, typename A = std::array<char, N>, size_t... Idx>
constexpr A substring_literal_as(const auto& str, std::index_sequence<Idx...>) noexcept
requires(requires(size_t n) {
{ str[n] } noexcept -> std::convertible_to<char>;
})
{
return { str[Idx]..., '\n' };
}
template<size_t... Idx>
constexpr auto substring_literal(const auto& str, std::index_sequence<Idx...>) noexcept
requires(std::is_invocable_v<substring_literal_as<sizeof...(Idx), std::array<char, sizeof...(Idx)>, Idx...>, decltype(str), std::index_sequence<Idx...>>)
{ return std::array{ str[Idx]..., '\n' }; }
template<typename T>
constexpr auto type_name_literal() noexcept
{
constexpr std::string_view prefix {
#if defined(__clang__)
"[T = "
#elif defined(__GNUC__)
"with T = "
#else
// Fuck MSVC, don't care.
#error Unsupported compiler
#endif
};
constexpr std::string_view suffix {"]"};
constexpr std::string_view function {__PRETTY_FUNCTION__};
constexpr auto start = function.find(prefix) + prefix.size();
constexpr auto end = function.rfind(suffix);
static_assert(start < end);
constexpr std::string_view name = function.substr(start, (end - start));
return substring_literal(name, std::make_index_sequence<name.size()>{});
}
template<typename T>
struct [[gnu::visibility("internal")]] type_name_of {
constexpr static inline auto value = type_name_literal<T>();
[[gnu::const]]
consteval operator std::string_view() const noexcept {
constexpr auto& v = value;
return std::string_view { v.data(), v.size() };
}
};
template<typename T>
constexpr auto type_name() noexcept -> std::string_view
{
constexpr auto& value = type_name_of<T>::value;
return std::string_view { value.data(), value.size() };
}
template<typename T>
constexpr inline auto type_name_v = type_name<T>();
template<typename S = std::string_view, S const&... Strs>
class [[gnu::visibility("internal")]] concat_str {
consteval static auto impl() noexcept
{
constexpr size_t len = (Strs.size() + ... + 0);
std::array<char, len+1> arr{};
auto append = [i=0, &arr](auto const& s) mutable {
for(auto c : s) arr[i++] = c;
};
(append(Strs), ...);
arr[len] = 0;
return arr;
}
public:
constexpr static inline auto literal = impl();
constexpr static inline S value { literal.data(), literal.size()-1 };
};
template<std::string_view const&... Ss>
constexpr static inline auto concat_str_v = concat_str<std::string_view, Ss...>::value;
template<typename S = std::string_view>
consteval S concat_strings(std::convertible_to<S> auto const&... strings) noexcept
{
return concat_str<S, S{strings}...>::value;
}
static_assert(concat_str_v< std::string_view{"hello"}, std::string_view{" "}, std::string_view{"view"} >
== std::string_view{"hello view"}, "concat_str_v<>: Concatenated string_view failed");
template<template<typename> P, typename... Values>
struct _EO(internal) is_all_match {
constexpr static inline auto value = (P<Values> && true && ...);
consteval operator auto() const noexcept { return value; }
};
template<template<typename> P, typename... Values>
struct _EO(internal) is_any_match {
constexpr static inline auto value = (P<Values> || false || ...);
consteval operator auto() const noexcept { return value; }
};
//XXX: Idk if template<template<s are allowed in this context...
template<template<typename> P, typename... Values> requires(requires{ typename is_all_match<P, Values>; })
constexpr inline auto is_all_match_v = is_all_match<P, Values...>::value;
template<template<typename> P, typename... Values> requires(requires{ typename is_any_match<P, Values>; })
constexpr inline auto is_any_match_v = is_any_match<P, Values...>::value;
template<auto... Values>
struct _EO(internal) is_all {
constexpr static inline auto value = (Values && true && ...);
};
template<auto... Values>
struct _EO(internal) is_any {
constexpr static inline auto value = (Values || false || ...);
};
template<auto... Values>
constexpr inline auto is_all_v = is_all<Values...>::value;
template<auto... Values>
constexpr inline auto is_any_v = is_any<Values...>::value;
template<typename... Args>
constexpr inline bool is_empty_v = sizeof...(Args) == 0;
template<auto... Values> // auto and typename aren't compatible unfortunately
constexpr inline bool is_empty_vv = sizeof...(Values) == 0;
// Convenience immediate function overloads for checking if a parameter pack *or* a value pack is empty without different names.
template<typename... A>
consteval bool is_empty_pack() noexcept { return sizeof...(A) == 0; }
template<auto... A>
consteval bool is_empty_pack() noexcept { return sizeof...(A) == 0; }
/// Deducable version of `is_empty_pack<typename...>()`
///
/// Can be used as `is_empty_pack(pack...)` instead of the slightly more ugly `is_empty_pack<decltype(pack)...>()` though with (very unlikely, but possible) implications of actually "evaluating" the pack (despite the values themselves are `const&` and never used, they're still passed.)
/// HOWEVER: The latter should likely be preferred, since this function takes possible-nonconstexpr types as arguments, it cannot be immediate like the non-argument taking overload. See below comment for more information on that.
template<typename... A>
[[gnu::const]] //TODO: XXX: This mostly useless function won't mess up overload resolution for `is_empty_pack<types...>()`, right? If it will, either find a way to make sure it comes LAST, or remove it, it's not worth the hassle just so users (i.e. me) can avoid typeing `decltype` for once in my fuckin life... what a stupud keyword name srsl (see static_assert() below.)
constexpr auto is_empty_pack(A const&...) noexcept { return is_empty_pack<A...>(); } // This overload exists for: e.g usage for index_sequence: \
`template<size_t N> f(const auto (&v)[N]) -> \
template<size_t Is...> _f(auto const& v, std::index_sequence<Is...>);`\
Can be used like: \
_f(const auto&v, std::convertible_to<size_t> auto&&... is) \
{ if constexpr(is_empty_pack(is...)) { ... } ... }` \
i.e: for unknown-typed non-type parameter packs `auto...`s packs can be `forward<decltype(pack)>(pack)...`'d (or just passed, as the taken value is always `const&`) to is_empty_pack(...) and `A...` can therefor be deduced. This is not possible for the non-value taking overloads, and also must be non-consteval since any `A` might not be constexpr. \
\
It is a convenience function that is essentially equivelent to `is_empty_pack<decltype(pack)...>()`, which should be preferred, since it is consteval and will always be guaranteed to be consteval regardless of the contents of `pack` itself, but I highly doubt any compiler will actually generate any argument-passing code for this overload either.
static_assert(!is_empty_pack<int, long, float>()
and is_empty_pack<>() //XXX: This might also be ambiguous between the type and value one... Eh.... That's actually useful though, so removing this explicit (and pointless) *usage* of the function from the test would be fine if they don't clash in other ways too.
and !is_empty_pack<0, 1, 2>()
and !is_empty_pack(0u, 1.0, 2l)
and is_empty_pack(), "`is_empty_pack()` overload issues.");
template<typename R, typename... Args>
constexpr auto map(auto const& fun, Args&&... values) noexcept(is_empty_v<Args...> or is_all_v<std::is_nothrow_invocable_v<decltype(fun), Args>...>)
requires(is_empty_v<Args...> or (is_all_v<std::is_invocable_v<decltype(fun), Args>...> and (
is_any_v<std::is_void_v<std::invoke_result_t<decltype(fun), Args>>...> ||
std::is_constructible_v<R, std::invoke_result_t<decltype(fun), Args>...>)))
-> std::conditional_t< is_empty_v<Args...> || is_any_v<std::is_void_v<std::invoke_result_t<decltype(fun), Args>>...>
, std::void_t<std::invoke_result_t<decltype(fun), Args>...>
, R>
{
if constexpr( is_any_v<std::is_void_v<std::invoke_result_t<decltype(fun), Args>>...> ) {
((void)std::invoke(fun, values),...);
else if constexpr(sizeof...(Args) == 0)
(void)0;
} else return { std::invoke(fun, values)... };
}
//XXX: To allow this, we might have to turn `map` from a function into a function-object class... Eh. \
Or we can just let the user specify `R` by using std::tuple as the lvalue assigned from the call maybe? Or we might have to change it to a `template<template<typename... Results>, typename... Args>` function-object class; but that would break the `void` case.... Eeeehh, can we partial-specialise a template<template into a template<typename? I don't think we can on the surface without helper ''trait'' templates.... Eh......
template<typename... Args>
using map_tuple = map<std::tuple<Args...>, Args...>;
//TODO: template< is_valid_metafunction_with_two_unknown_type_arguments_and_a_possibly_variable_return_type<R(T1, T2)> Op, typename... Args> \
auto reduce(auto const& fun, Op const& comb, Args&&... values) noexcept(...) \
requires(???) -> std::invoke_result_t<??????> { // TODO: Reframe the call to `comb` as an operator overload in a shim class, and use binary fold to make the call, like: ` \
shim s{comb}; return (s + std::invoke(fun, values) + ...); }`
template<typename Fn, typename... Args> requires((std::is_invocable_v<Fn, Args> && ...))
constexpr void apply(const Fn& fun, Args&&... values) noexcept((std::is_nothrow_invocable_v<Fn, Args> && ...))
/*-> std::conditional_t< (std::is_void_v<std::invoke_result_t<Fn, Args>> || ...)
, void
, std::common_type_t< std::invoke_result_t<Fn, Args>... > >*/
{ map<void, Args...>(fun, std::forward<Args>(values)...); }
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;
};
/// Throw a string at runtime, as an `std::runtime_error`.
[[noreturn, gnu::noinline, gnu::cold]]
void throw_runtime(std::string_view&&);
/// Throw a string at runtime, or halt compilation with a `CTInternalFatalError`
[[noreturn]]//, gnu::noinline, gnu::cold]]
constexpr void throw_runtime(std::convertible_to<std::string_view> auto&& msg) {
if consteval {
#define CTE CTInternalFatalError
using S = decltype(msg);
struct CTIFRuntimeError final : public CTE {
using CTE::CTE;
consteval CTIFRuntimeError(S&& view) noexcept
: CTE(), m_message(std::move(view)) {}
consteval std::string_view message() const noexcept override { return { m_message }; }
constexpr virtual ~CTIFRuntimeError() noexcept {}
private:
S m_message;
};
#undef CTE
throw CTIFRuntimeError(std::move(msg));
} else {
static_cast<void (std::string_view&&)>(throw_runtime)({std::move(msg)}); //XXX: We may have to rename the above function (and make it [[internal]], or refactor to make it a private method of a function-object class `throw_runtime` to avoid ambiguations when constexpr-context calls it with the exact typed argument `std::string_view&&`... Maybe... I'm not really sure.
}
__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>()");
}
} }