consteval std::string_view RawTypeName() {
return std::source_location::current().function_name();
}
// The part of RawName()'s text that spells P: what follows "Probe = "
// up to the closing bracket.
consteval std::string_view ProbeText(std::string_view raw) {
const std::size_t end = raw.rfind(']');
const std::size_t start = raw.rfind("Probe = ", end);
return raw.substr(start + 8, end - start - 8);
}
// The enumerator name for V, or "" when V is not an enumerator: clang
// spells those as a cast, "(Ns::Color)5".
template
consteval std::string_view EnumeratorName() {
const std::string_view text = ProbeText(RawName());
if (text.starts_with('(')) return {};
const std::size_t colon = text.rfind("::");
return colon == std::string_view::npos ? text : text.substr(colon + 2);
}
}
export namespace Crafter::Debug {
// True for class types whose non-static data members a structured
// binding can bind: public members, no bases carrying members, no
// user-declared constructors.
template
concept Aggregate = std::is_class_v && std::is_aggregate_v && requires { Reflect::CountHelper::value; };
template
inline constexpr std::size_t memberCount = Reflect::CountHelper::value;
// The I-th member of an aggregate lvalue.
template
constexpr decltype(auto) Member(T&& t) noexcept {
auto&& [...m] = t;
return (m...[I]);
}
template
using MemberType = std::remove_cvref_t(std::declval()))>;
// The declared name of the I-th member: "yaw". Parsed out of
// "std::string_view ...::RawName() [Probe = &external.yaw]".
template
consteval std::string_view MemberName() {
constexpr std::string_view text = Reflect::ProbeText(Reflect::RawName<&Member(Reflect::external)>());
return text.substr(text.rfind('.') + 1);
}
// The type's name as the compiler spells it ("Game::AimArgs"), for help
// and errors; the spelling is not load-bearing.
template
consteval std::string_view TypeName() {
constexpr std::string_view raw = Reflect::RawTypeName();
const std::size_t end = raw.rfind(']');
const std::size_t start = raw.rfind("T = ", end);
return raw.substr(start + 4, end - start - 4);
}
// Calls f.template operator()(member) for every member, in order.
template
constexpr void ForEachMember(T& t, F&& f) {
[&](std::index_sequence) {
(f.template operator()(Member(t)), ...);
}(std::make_index_sequence>{});
}
// The values probed for enumerator names, a half-open range clamped to
// the enum's underlying type. Specialize for an enum with values outside
// the default range; every probe is one template instantiation.
template
struct EnumRange {
static constexpr std::int64_t min = -128;
static constexpr std::int64_t max = 128;
};
template
struct Enumerator {
E value;
std::string_view name;
};
}
namespace Crafter::Debug::Reflect {
template
consteval std::int64_t ProbeMin() {
using U = std::underlying_type_t;
return std::max(EnumRange::min, static_cast(std::numeric_limits::min()));
}
template
consteval std::int64_t ProbeMax() {
using U = std::underlying_type_t;
constexpr std::int64_t limit = static_cast(std::numeric_limits::max()) >= static_cast(std::numeric_limits::max())
? std::numeric_limits::max()
: static_cast(std::numeric_limits::max()) + 1;
return std::min(EnumRange::max, limit);
}
template
consteval auto EnumTable(std::index_sequence) {
constexpr std::int64_t first = ProbeMin();
constexpr std::array names = {
EnumeratorName(first + static_cast(I))>()...
};
constexpr std::size_t count = static_cast(std::ranges::count_if(names, [](std::string_view name) { return !name.empty(); }));
std::array, count> table{};
std::size_t next = 0;
for (std::size_t i = 0; i < names.size(); ++i) {
if (!names[i].empty()) table[next++] = { static_cast(first + static_cast(i)), names[i] };
}
return table;
}
}
export namespace Crafter::Debug {
// Every named value of a scoped enum within EnumRange, in value order.
// Scoped only: clang rejects casting an out-of-range value to an unscoped
// enum without a fixed underlying type, even in a template argument.
template
requires std::is_scoped_enum_v
inline constexpr auto enumerators = Reflect::EnumTable(std::make_index_sequence(Reflect::ProbeMax() - Reflect::ProbeMin())>{});
template
requires std::is_scoped_enum_v
constexpr std::optional EnumName(E value) {
for (const Enumerator& e : enumerators) {
if (e.value == value) return e.name;
}
return std::nullopt;
}
template
requires std::is_scoped_enum_v
constexpr std::optional EnumFromName(std::string_view name) {
for (const Enumerator& e : enumerators) {
if (e.name == name) return e.value;
}
return std::nullopt;
}
}
// Self-tests: a compiler whose function_name() text changes shape fails
// here, at the library's build, instead of producing garbage names.
namespace Crafter::Debug::Reflect {
struct ProbeStruct {
std::int32_t alpha;
double beta;
std::string gamma;
};
enum class ProbeEnum : std::uint8_t { Zero, One, Seven = 7 };
enum class ProbeSigned : std::int16_t { Minus = -3, Big = 100 };
static_assert(Aggregate);
static_assert(memberCount == 3);
static_assert(MemberName<0, ProbeStruct>() == "alpha");
static_assert(MemberName<2, ProbeStruct>() == "gamma");
static_assert(std::is_same_v, double>);
static_assert(TypeName().ends_with("ProbeStruct"));
static_assert(enumerators.size() == 3);
static_assert(EnumName(ProbeEnum::Seven) == "Seven");
static_assert(!EnumName(static_cast(5)).has_value());
static_assert(EnumFromName("One") == ProbeEnum::One);
static_assert(EnumName(ProbeSigned::Minus) == "Minus");
static_assert(enumerators.size() == 2);
}