Changed `NullException` to add optional type information. Added constexpr util::type_of<T>(), added constexpr `std::string_view` concatenation: `util::concat_str_v<std::string_view...>`, added `std::string_view util::concat_strings(auto const&...)`.
Fortune for libexopt's current commit: Future blessing − 末吉boxed_is_boxed_value
parent
a7e4a3ffcc
commit
2fad212234
@ -0,0 +1,123 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <utility>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "pointer.h"
|
||||||
|
#include "util.hh"
|
||||||
|
|
||||||
|
#include "exopt.h"
|
||||||
|
|
||||||
|
namespace exopt::types {
|
||||||
|
namespace polyarray [[gnu::visibility("internal")]] {
|
||||||
|
|
||||||
|
template<typename T, size_t N>
|
||||||
|
struct SizedArray;
|
||||||
|
|
||||||
|
struct UnsizedAccessor {
|
||||||
|
constexpr virtual ~UnsizedAccessor() = default;
|
||||||
|
|
||||||
|
constexpr virtual T* data() const noexcept =0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T, size_t N>
|
||||||
|
struct SizedAccessor : virtual UnsizedAccessor {
|
||||||
|
//using UnsizedAccessor::UnsizedAccessor;
|
||||||
|
constexpr virtual ~SizedAccessor() = default;
|
||||||
|
|
||||||
|
constexpr T* data() const noexcept override final { return raw()->data(); }
|
||||||
|
|
||||||
|
[[gnu::returns_nonnull]]
|
||||||
|
constexpr virtual std::array<T, N>* raw() noexcept =0;
|
||||||
|
[[gnu::returns_nonnull]]
|
||||||
|
constexpr virtual std::array<T, N> const* raw() const noexcept =0;
|
||||||
|
|
||||||
|
constexpr virtual std::array<T, N>& array() & noexcept { return *raw(); }
|
||||||
|
constexpr virtual std::array<T, N> const& array() const& noexcept { return *raw(); }
|
||||||
|
|
||||||
|
constexpr virtual std::array<T, N>&& array() && noexcept { return std::move(*raw()); }
|
||||||
|
constexpr virtual std::array<T, N> const&& array() const&& noexcept { return std::move(*raw()); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An `std::array<T>` with unknown compile-time size.
|
||||||
|
template<typename T>
|
||||||
|
class PolyArray : public virtual UnsizedAccessor {
|
||||||
|
public:
|
||||||
|
constexpr PolyArray() noexcept = default;
|
||||||
|
constexpr virtual ~PolyArray() = default;
|
||||||
|
|
||||||
|
using UnsizedAccessor::data; //constexpr virtual T* data() const noexcept =0; Inherited from UnsizedAccessor
|
||||||
|
constexpr virtual size_t size() const noexcept =0;
|
||||||
|
//TODO: std::array-like accessors based on `data()..size()`
|
||||||
|
|
||||||
|
template<size_t N>
|
||||||
|
constexpr std::array<T, N>* try_downcast() noexcept {
|
||||||
|
if consteval {
|
||||||
|
static_assert(size() == N, "Invalid downcast array size");
|
||||||
|
}
|
||||||
|
if (size() == N) { return static_cast<SizedAccessor<T, N>*>(accessor())->raw(); } //reinterpret_cast<std::array<T, N>*>(unsafe_data()); }
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
template<size_t N>
|
||||||
|
constexpr std::array<T, N>&& downcast() && {
|
||||||
|
if(auto* nar = try_downcast()) {
|
||||||
|
return std::move(*nar);
|
||||||
|
} else throw_invalid_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool empty() const noexcept { return !size(); }
|
||||||
|
protected:
|
||||||
|
[[noreturn, gnu::noinline, gnu::cold]]
|
||||||
|
constexpr void throw_invalid_size() {
|
||||||
|
std::terminate(); //TODO: throw `poly_array_size_exception`
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[gnu::returns_nonnull]]
|
||||||
|
constexpr UnsizedAccessor* accessor() noexcept { return static_cast<UnsizedAccessor*>(this); }
|
||||||
|
[[gnu::returns_nonnull]]
|
||||||
|
constexpr const UnsizedAccessor* accessor() const noexcept { return static_cast<UnsizedAccessor const*>(this); }
|
||||||
|
|
||||||
|
template<size_t N>
|
||||||
|
[[gnu::returns_nonnull]]
|
||||||
|
constexpr SizedAccessor<T, N>* unsafe_cast() noexcept { return static_cast<SizedAccessor<T, N>*>(
|
||||||
|
static_cast<UnsizedAccessor*>(this)); }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T, size_t N>
|
||||||
|
struct SizedArray : virtual /*XXX: Needed? since both non-virtual bases of this class have virtual base UnsizedAccessor?*/ SizedAccessor<T, N>
|
||||||
|
, public PolyArray<T> {
|
||||||
|
[[gnu::const]]
|
||||||
|
constexpr size_t size() const noexcept override final { return N; }
|
||||||
|
//[[gnu::const, gnu::returns_nonnull]] // Unneeded, SizedAccessor implements it as raw()->data()
|
||||||
|
//constexpr T* data() const noexcept override final { return m_data.data(); }
|
||||||
|
|
||||||
|
constexpr SizedArray(std::array<T, N>&& m) noexcept
|
||||||
|
: m_data(std::move(m)) {}
|
||||||
|
//TODO: ctors, assign, etc.
|
||||||
|
|
||||||
|
constexpr virtual ~SizedArray() = default;
|
||||||
|
//TODO: operator*() decltype(auto) forwards from SizedAccessor::array()
|
||||||
|
protected:
|
||||||
|
[[gnu::returns_nonnull]]
|
||||||
|
constexpr std::array<T, N>* raw() noexcept override final { return std::addressof(m_data); }
|
||||||
|
[[gnu::returns_nonnull]]
|
||||||
|
constexpr std::array<T, N> const* raw() const noexcept override final { return std::addressof(m_data); }
|
||||||
|
private:
|
||||||
|
std::array<T, N> m_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T, size_t N>
|
||||||
|
constexpr std::unique_ptr<PolyArray<T>> make_unsized(std::array<T, N>&& array) noexcept {
|
||||||
|
auto sub = std::make_unique<SizedArray<T, N>>(std::move(array));
|
||||||
|
return std::unique_ptr<PolyArray<T>> { static_cast<PolyArray<T>*>(sub.release()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, size_t N>
|
||||||
|
constexpr std::array<T, N>&& /*XXX: <- Is returning an rvalue-ref here acceptible? When is `array` dropped here? Does the caller actuall drop it or the std::move() in here...? We aren't actually assigning the move to anything at all, we're just working through rvalue-refs, so it *should* be fine...??? Fuck this language honestly... */ make_sized(std::unique_ptr<PolyArray<T>>&& array) {
|
||||||
|
return (*std::move(array)).downcast<T, N>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <bit>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "util.hh"
|
||||||
|
|
||||||
|
#include "exopt.h"
|
||||||
|
|
||||||
|
namespace exopt::rng {
|
||||||
|
[[gnu::visibility("internal")]]
|
||||||
|
constexpr inline auto COMPILED_ENTROPY{
|
||||||
|
#if __has_include("compiled_entropy.dat")
|
||||||
|
#embed "compiled_entropy.dat"
|
||||||
|
#else
|
||||||
|
nullptr
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
constexpr inline auto has_compiled_entropy_v = !std::is_same_v<std::nullptr_t, decltype(COMPILED_ENTROPY)>;
|
||||||
|
|
||||||
|
// XXX: The usage of __COUNTER__ *might* work as wanted if we wrap it in _EO_CONSTANT_VALUE() and take `util::comptime<uint64_t> auto = _EO_CONSTANT_VALUE(__COUNTER__)` as default argument instead. Maybe... I don't know..
|
||||||
|
template<typename T = unsigned char>
|
||||||
|
consteval decltype(auto) comptime_rand_next() noexcept {
|
||||||
|
std::array<unsigned char, sizeof(T)> v;
|
||||||
|
for(int i=0;i<v.size();i++) v[i] = comptime_rand_next<unsigned char>();
|
||||||
|
return util::comptime_value(std::bit_cast<T>(std::move(v)));
|
||||||
|
}
|
||||||
|
template<>
|
||||||
|
consteval decltype(auto) comptime_rand_next<unsigned char>(uint64_t counter = __COUNTER__) noexcept {
|
||||||
|
return _EO_CONSTANT_VALUE(COMPILED_ENTROPY[counter % sizeof(COMPILED_ENTROPY)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
consteval uint64_t seed_linear(uint64_t counter = __COUNTER__) noexcept { return uint64_t(counter); } //XXX: This is kinda a dumb experiment, the __TIME__ TU-specific seed should be fine, but __COUNTER__ is a token evaluated regardless of calling context, so... Unless it's used before the #include<>, it'll always be the same.
|
||||||
|
consteval uint64_t seed_translation() noexcept { return uint64_t(__TIME__); }
|
||||||
|
|
||||||
|
|
||||||
|
// Seed: __TIME__ or __COUNTER__
|
||||||
|
[[gnu::const]]
|
||||||
|
constexpr uint64_t splitmix64_once(uint64_t x) noexcept {
|
||||||
|
//TODO: implement splitmix64 here for constexpr/consteval contexts
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr inline uint64_t translation_unit_fixed_seed = splitmit64_once(seed_translation());
|
||||||
|
constexpr inline uint64_t linear_fixed_seed = splitmit64_once(seed_linear()); // ODR dependant?? When is __COUNTER__ evaluated? In preprocessing, right? So, before any compilation?
|
||||||
|
constexpr inline auto translation_unit_rolling_seed = ([] () {
|
||||||
|
struct {
|
||||||
|
constexpr operator uint64_t() const noexcept { return splitmix64_once(translation_unit_fixed_seed ^ seed_linear()) ^ _EO_CONSTANT_VALUE(splitmix64_once(__COUNTER__)); }
|
||||||
|
} inner;
|
||||||
|
return inner;
|
||||||
|
})();
|
||||||
|
}
|
Loading…
Reference in new issue