Info
-
Did you know that C++20
source_location
can be used to get the member names?
Example
namespace detail {
template <class, auto>
[[nodiscard]] consteval auto member_name() -> std::string_view {
return std::source_location::current().function_name();
}
template <class T> extern const T external;
consteval auto get(auto& obj) {
auto& [p1] = obj;
return &p1;
}
} // namespace deatil
template <class T>
constexpr auto member_name = detail::member_name<T, detail::get(detail::external<T>)>();
struct foo {
int bar;
};
static_assert(member_name<foo>.find("bar") != std::string_view::npos);
Puzzle
- Can you implement
to_tuple
which returns a tuple from a struct with names?
template <fixed_string Name, class T>
struct named {
static constexpr auto name = Name;
T value{};
};
[[nodiscard]] consteval auto to_tuple(auto&& t); // TODO
struct foo {
int a;
int b;
};
constexpr auto t = to_tuple(foo{.a=42, .b=87});
static_assert("a" == std::get<0>(t).name and 42 == std::get<0>(t).value);
static_assert("b" == std::get<1>(t).name and 87 == std::get<1>(t).value);
Solutions
template <std::size_t N>
class fixed_string final {
public:
constexpr explicit(true) fixed_string(const auto... cs) : data{cs...} {}
constexpr explicit(false) fixed_string(const char (&str)[N + 1]) {
std::copy_n(str, N + 1, std::data(data));
}
[[nodiscard]] constexpr auto operator<=>(const fixed_string&) const =
default;
[[nodiscard]] constexpr explicit(false) operator std::string_view() const {
return {std::data(data), N};
}
[[nodiscard]] constexpr auto size() const -> std::size_t { return N; }
std::array<char, N + 1> data{};
};
template <std::size_t N>
fixed_string(const char (&str)[N]) -> fixed_string<N - 1>;
template <fixed_string Name, class T>
struct named {
static constexpr auto name = Name;
T value{};
};
struct any_type {
template <class T>
constexpr operator T();
};
template <class TPtr>
struct ptr {
const TPtr* ptr;
};
namespace detail {
template <class T>
extern const T external;
struct any_type {
template <class T>
constexpr operator T();
};
template <class TPtr>
struct ptr {
const TPtr* ptr;
};
template <auto N, class T>
[[nodiscard]] constexpr auto nth_ptr(T&& t) {
if constexpr (requires { T{any_type{}, any_type{}, any_type{}}; }) {
auto&& [p1, p2, p3] = t;
if constexpr (N == 0) return ptr<decltype(p1)>{&p1};
if constexpr (N == 1) return ptr<decltype(p2)>{&p2};
if constexpr (N == 2) return ptr<decltype(p3)>{&p3};
} else if constexpr (requires { T{any_type{}, any_type{}}; }) {
auto&& [p1, p2] = t;
if constexpr (N == 0) return ptr<decltype(p1)>{&p1};
if constexpr (N == 1) return ptr<decltype(p2)>{&p2};
} else if constexpr (requires { T{any_type{}}; }) {
auto&& [p1] = t;
if constexpr (N == 0) return ptr<decltype(p1)>{&p1};
}
}
template <auto Ptr>
[[nodiscard]] consteval auto get_name() -> std::string_view {
return std::source_location::current().function_name();
}
template <auto N, class T>
constexpr auto get_name_impl =
detail::get_name<detail::nth_ptr<N>(detail::external<T>)>();
struct $struct$ {
int $field$;
};
constexpr auto $name = get_name_impl<0, detail::$struct$>;
constexpr auto $end =
$name.substr($name.find("$field$") + sizeof("$field$") - 1);
constexpr auto $begin = $name[$name.find("$field$") - 1];
} // namespace detail
template <auto N, class T>
constexpr auto get_name = [] {
const auto name = detail::get_name_impl<N, T>;
const auto begin = name.find(detail::$end);
const auto tmp = name.substr(0, begin);
return tmp.substr(tmp.find_last_of(detail::$begin) + 1);
}();
template <auto N>
[[nodiscard]] consteval auto nth(auto... args) {
return [&]<std::size_t... Ns>(std::index_sequence<Ns...>) {
return [](decltype((void*)Ns)..., auto* nth, auto*...) {
return *nth;
}(&args...);
}(std::make_index_sequence<N>{});
}
template <auto N, class T>
constexpr auto get(T&& t) {
if constexpr (requires { T{any_type{}, any_type{}, any_type{}}; }) {
auto&& [p1, p2, p3] = t;
// structure bindings is not constexpr :/
if constexpr (N == 0) return ptr<decltype(p1)>{&p1};
if constexpr (N == 1) return ptr<decltype(p2)>{&p2};
if constexpr (N == 2) return ptr<decltype(p3)>{&p3};
} else if constexpr (requires { T{any_type{}, any_type{}}; }) {
auto&& [p1, p2] = t;
// structure bindings is not constexpr :/
if constexpr (N == 0) return ptr<decltype(p1)>{&p1};
if constexpr (N == 1) return ptr<decltype(p2)>{&p2};
} else if constexpr (requires { T{any_type{}}; }) {
auto&& [p1] = t;
// structure bindings is not constexpr :/
if constexpr (N == 0) return ptr<decltype(p1)>{&p1};
}
}
template <class T, auto N>
[[nodiscard]] consteval auto member_name() {
constexpr auto name = get_name<N, T>;
return [&]<auto... Ns>(std::index_sequence<Ns...>) {
return fixed_string<sizeof...(Ns)>{name[Ns]...};
}(std::make_index_sequence<name.size()>{});
}
template <class T>
[[nodiscard]] constexpr auto to_tuple(const T& t) {
if constexpr (requires { T{any_type{}, any_type{}, any_type{}}; }) {
auto&& [p1, p2, p3] = t;
return std::tuple(
named<member_name<T, 0>(), decltype(p1)>{.value = p1},
named<member_name<T, 1>(), decltype(p2)>{.value = p2},
named<member_name<T, 2>(), decltype(p2)>{.value = p3});
} else if constexpr (requires { T{any_type{}, any_type{}}; }) {
auto&& [p1, p2] = t;
return std::tuple(
named<member_name<T, 0>(), decltype(p1)>{.value = p1},
named<member_name<T, 1>(), decltype(p2)>{.value = p2});
} else if constexpr (requires { T{any_type{}}; }) {
auto&& [p1] = t;
return std::tuple(
named<member_name<T, 0>(), decltype(p1)>{.value = p1});
} else {
return std::tuple();
}
}
struct foo {
int a;
int b;
};
constexpr auto t = to_tuple(foo{.a=42, .b=87});
static_assert("a" == std::get<0>(t).name and 42 == std::get<0>(t).value);
static_assert("b" == std::get<1>(t).name and 87 == std::get<1>(t).value);