175 lines
5.6 KiB
C++
175 lines
5.6 KiB
C++
//-----------------------------------------------------------------------------
|
|
// ___ __ _ _
|
|
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
|
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
|
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
|
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
// Author: Kurt Sassenrath
|
|
// Module: Utility
|
|
//
|
|
// Compile-time reflection for structures.
|
|
//
|
|
// Copyright (c) 2023 Kurt Sassenrath.
|
|
//
|
|
// License TBD.
|
|
//-----------------------------------------------------------------------------
|
|
#ifndef parselink_ct_reflect_080596cd36d52770
|
|
#define parselink_ct_reflect_080596cd36d52770
|
|
|
|
#include <array>
|
|
#include <source_location>
|
|
#include <string_view>
|
|
#include <tuple>
|
|
|
|
namespace parselink {
|
|
namespace ct {
|
|
|
|
namespace detail {
|
|
|
|
// This allows us to reference a type instance without ever instantiating it.
|
|
template <typename T>
|
|
extern T const phantom;
|
|
|
|
// This type binds to anything, allowing us to sus out the number of data
|
|
// members in an aggregate.
|
|
struct any_type {
|
|
template <typename T>
|
|
constexpr operator T();
|
|
};
|
|
|
|
// clang-format off
|
|
// This template takes a wrapped pointer to a data member of a theoretical
|
|
// instance of that type, in order to retrieve the name at compile time.
|
|
// Examples:
|
|
// gcc:
|
|
// constexpr auto parselink::ct::detail::src_string() [with auto Member = const_ptr<int>{(& phantom<structure>.structure::field)}]
|
|
//
|
|
// clang:
|
|
// auto parselink::ct::detail::src_string() [Member = const_ptr<member_ptr>{&phantom.field}]
|
|
// clang-format on
|
|
template <auto Member>
|
|
constexpr auto src_string() {
|
|
return std::source_location::current().function_name();
|
|
}
|
|
|
|
template <char StartDelim, char EndDelim>
|
|
struct base_parse_traits {
|
|
static constexpr char start_delim = StartDelim;
|
|
static constexpr char end_delim = EndDelim;
|
|
};
|
|
|
|
#ifdef __clang__
|
|
struct parse : base_parse_traits<'.', '}'> {};
|
|
#elif defined(__GNUC__)
|
|
struct parse : base_parse_traits<':', ')'> {};
|
|
#endif
|
|
|
|
static constexpr std::size_t max_members = 8;
|
|
|
|
// TODO: Logarithmic approach
|
|
// This needs to be limited to max_members, otherwise clang will go off into
|
|
// the weeds.
|
|
template <typename T, typename... Members>
|
|
requires(sizeof...(Members) <= max_members)
|
|
consteval std::size_t count_members(Members&&... members) {
|
|
if constexpr (requires { T{any_type{}, members...}; } == false) {
|
|
return sizeof...(members);
|
|
} else if constexpr (sizeof...(members) <= max_members) {
|
|
return count_members<T>(members..., any_type{});
|
|
} else {
|
|
throw "Too many members / something went wrong!";
|
|
}
|
|
};
|
|
|
|
// This function takes an aggregate and attempts to bind it into a tuple.
|
|
// Unfortunately, without variadic packs for structured bindings, this will
|
|
// rely on some heavy boilerplate.
|
|
template <typename T, std::size_t MemberCount = count_members<T>()>
|
|
requires(MemberCount <= 8)
|
|
consteval decltype(auto) as_tuple(T&& t) {
|
|
if constexpr (MemberCount == 0) {
|
|
return std::tuple{};
|
|
} else if constexpr (MemberCount == 1) {
|
|
auto&& [a0] = t;
|
|
return std::tie(a0);
|
|
} else if constexpr (MemberCount == 2) {
|
|
auto&& [a0, a1] = t;
|
|
return std::tie(a0, a1);
|
|
} else if constexpr (MemberCount == 3) {
|
|
auto&& [a0, a1, a2] = t;
|
|
return std::tie(a0, a1, a2);
|
|
} else if constexpr (MemberCount == 4) {
|
|
auto&& [a0, a1, a2, a3] = t;
|
|
return std::tie(a0, a1, a2, a3);
|
|
} else if constexpr (MemberCount == 5) {
|
|
auto&& [a0, a1, a2, a3, a4] = t;
|
|
return std::tie(a0, a1, a2, a3, a4);
|
|
} else if constexpr (MemberCount == 6) {
|
|
auto&& [a0, a1, a2, a3, a4, a5] = t;
|
|
return std::tie(a0, a1, a2, a3, a4, a5);
|
|
} else if constexpr (MemberCount == 7) {
|
|
auto&& [a0, a1, a2, a3, a4, a5, a6] = t;
|
|
return std::tie(a0, a1, a2, a3, a4, a5, a6);
|
|
} else if constexpr (MemberCount == 8) {
|
|
auto&& [a0, a1, a2, a3, a4, a5, a6, a7] = t;
|
|
return std::tie(a0, a1, a2, a3, a4, a5, a6, a7);
|
|
} else {
|
|
throw "Too many members!";
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
struct const_ptr {
|
|
T const* ptr;
|
|
};
|
|
|
|
template <std::size_t I, typename T>
|
|
consteval decltype(auto) as_ptr(T&& t) noexcept {
|
|
auto&& member = std::get<I>(as_tuple(t));
|
|
using member_ptr = std::decay_t<decltype(member)>;
|
|
return const_ptr<member_ptr>(&member);
|
|
}
|
|
|
|
template <typename T, std::size_t I>
|
|
consteval decltype(auto) raw_member_name() noexcept {
|
|
return src_string<as_ptr<I>(phantom<T>)>();
|
|
}
|
|
|
|
template <typename T, std::size_t I>
|
|
consteval decltype(auto) get_member_name() noexcept {
|
|
std::string_view raw = raw_member_name<T, I>();
|
|
auto s = raw.rfind(parse::start_delim);
|
|
auto e = raw.rfind(parse::end_delim);
|
|
if (s == raw.npos || e == raw.npos) throw "name parsing is broken!";
|
|
return raw.substr(s + 1, e - (s + 1));
|
|
}
|
|
|
|
template <typename T, std::size_t... Is>
|
|
consteval decltype(auto) get_member_names(std::index_sequence<Is...>) noexcept {
|
|
return std::array{get_member_name<T, Is>()...};
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
template <typename T, std::size_t I>
|
|
consteval decltype(auto) member_name() noexcept {
|
|
std::string_view raw = detail::raw_member_name<T, I>();
|
|
auto s = raw.rfind(detail::parse::start_delim);
|
|
auto e = raw.rfind(detail::parse::end_delim);
|
|
if (s == raw.npos || e == raw.npos) throw "name_of is broken!";
|
|
return raw.substr(s + 1, e - (s + 1));
|
|
}
|
|
|
|
template <typename T>
|
|
consteval decltype(auto) member_names() noexcept {
|
|
return detail::get_member_names<T>(
|
|
std::make_index_sequence<detail::count_members<T>()>{});
|
|
}
|
|
|
|
} // namespace ct
|
|
} // namespace parselink
|
|
|
|
#endif // parselink_ct_reflect_080596cd36d52770
|