Reflection
I'm looking into simplifying boilerplate code. Use cases:
- json
- database ORMs
- language bindings (lua, c, rust)
- enum listing, to/from string
Templated way
There's a function boost::pfr::get_name in Boost that implements compiler-agnostic way to determine field name. Let's see if we can implement this as well and find possible usecases.
There's no a template based solution to iterate through fields and pick up their names though. I'm not even talking about the complexity and expertness needed to make such a solution.
Codegen way
I recognize the following ways we can accomplish that:
#define AUI_ATTR(...)
AUI_ATTR(json)
struct User {
int id;
AString name;
};
The define AUI_ATTR is a stub for the C++ compiler to ignore DSL in braces. This DSL is parsed with a separate build time tool (i.e. aui.toolbox). I would call this approach as aui.cekc, which stands for aui c++ extentions kinda compiler.
The compiler generates a header/cpp where these fields with their attributes can be examined.
template<>
struct aui::reflect::json<User> {
static constexpr auto FIELDS = std::make_tuple(
/* up to discussion */
std::make_tuple("id", &User::id),
std::make_tuple("name", &User::name)
);
}
This approach is similar to https://www.youtube.com/watch?v=9DtK49lmNkk
Instead of #define AUI_ATTR, we can abuse C++ attributes:
[[aui::json]]
struct User {
int id;
AString name;
};
The latter is very similar to https://github.com/OpenSpace/codegen/tree/master
I want this as much flexible as possible. May be we can provide a way to customize the generated code.
https://www.reddit.com/r/cpp/comments/krn0g7/comment/giateyk/
#include <cassert>
#include <cstddef>
struct Struct { int a; int b; };
template<typename T, typename U>
constexpr size_t offsetOf(U T::*member) {
return (std::byte*)&((T*)nullptr->*member) - (std::byte*)nullptr;
}
template <class T, class U>
constexpr U& imaginary_syntax(T& t, T U::*ptr) {
return *reinterpret_cast<U*>(reinterpret_cast<std::byte*>(&t) - offsetOf(ptr));
}
int main() {
int Struct::* mem_ptr_b = &Struct::b;
Struct s = { 1,2 };
int& b_in_s = s.b;
// This exists:
int& also_b_in_s = s.*mem_ptr_b;
assert(&b_in_s == &also_b_in_s);
// I want to go the other way:
Struct& also_s = imaginary_syntax(b_in_s, mem_ptr_b);
assert(&s == &also_s);
}
#if AUI_REFLECT_FIELD_NAMES_ENABLED
namespace detail {
#if AUI_COMPILER_MSVC
/**
* @brief External linkage wrapper, so T is not enforced to be externally linked.
*/
template <class T>
struct wrapper {
const T value;
};
/**
* @brief Link time assert. If linker fails to link with it, it means that fake_object used in run time.
*/
template <class T>
extern const wrapper<T> DO_NOT_USE_REFLECTION_WITH_LOCAL_TYPES;
/**
* @brief For returning non-default constructible types.
* @tparam T
* @details
* Neither std::declval nor unsafe_declval are suitable here.
*/
template <class T>
constexpr const T &fake_object() noexcept {
return DO_NOT_USE_REFLECTION_WITH_LOCAL_TYPES<T>.value;
}
template <
class UniqueKey, // https://developercommunity.visualstudio.com/t/__FUNCSIG__-outputs-wrong-value-with-C/10458554
auto M>
consteval std::string_view name_of_field_impl() noexcept {
std::string_view s = __FUNCSIG__;
s = s.substr(s.rfind("->value->"));
s = s.substr(sizeof("->value->") - 1);
s = s.substr(0, s.rfind(">(void)"));
return s;
}
template <
class UniqueKey, // https://developercommunity.visualstudio.com/t/__FUNCSIG__-outputs-wrong-value-with-C/10458554
auto M>
consteval std::string_view name_of_field_impl_method() noexcept {
std::string_view s = __FUNCSIG__;
s = s.substr(s.rfind(':') + 1);
s = s.substr(0, s.find('('));
return s;
}
#endif
template<typename Clazz, auto M>
consteval std::string_view field_name() {
#if AUI_COMPILER_MSVC
constexpr bool isMemberField = requires (Clazz&& z) { std::addressof(z.*M); };
if constexpr (isMemberField) {
return detail::name_of_field_impl<
Clazz,
std::addressof(std::addressof(detail::fake_object<Clazz>())->*M)>();
} else {
return detail::name_of_field_impl_method<Clazz, M>();
}
#elif AUI_COMPILER_CLANG
std::string_view s = __PRETTY_FUNCTION__;
{
auto last = s.rfind(']');
auto begin = s.rfind('&');
s = s.substr(begin, last - begin);
}
if (auto c = s.rfind(':')) {
s = s.substr(c + 1);
}
return s;
#else
std::string_view s = __PRETTY_FUNCTION__;
{
auto last = s.rfind(';');
auto begin = s.rfind('&');
s = s.substr(begin, last - begin);
}
if (auto c = s.rfind(':')) {
s = s.substr(c + 1);
}
return s;
#endif
}
} // namespace detail
#endif
/**
* @brief Pointer to member value (not type) introspection.
* @ingroup reflection
* @details
* In comparison to @ref aui::reflect::member, aui::reflect::member_v inspects pointer-to-member value, instead of type.
* Since value points to exact member instead of generalizing by type, this allows to introspect additional data such as
* member name:
*
* @snippet aui.core/src/AUI/Reflect/members.h getName
*
* # Example
*
* @snippet aui.core/tests/ReflectTest.cpp member_v
* @snippet aui.core/tests/ReflectTest.cpp member_v2
*
* # Derived data from aui::reflect::member
* aui::reflect::member_v derives members from @ref aui::reflect::member.
*
* @copydetails aui::reflect::member
*/
template <auto M>
struct member_v : member<decltype(M)> {
#if AUI_REFLECT_FIELD_NAMES_ENABLED
/// [getName]
/*
* @brief Field name.
* @details
* Compile-time `std::string_view` that holds name of the field.
*
* It's implemented via forbidden compiler-specific magic and requires your `class`/`struct` to be defined with
* external linkage, i.e., please do not use function local types.
*/
static constexpr std::string_view name = detail::field_name<typename member_v::clazz, M>();
/// [getName]
#endif
};
//static_assert(member_v<&std::string_view::data>::name == "data");
#if AUI_REFLECT_FIELD_NAMES_ENABLED
namespace detail {
#if AUI_COMPILER_MSVC
/**
* @brief External linkage wrapper, so T is not enforced to be externally linked.
*/
template <class T>
struct wrapper {
const T value;
};
/**
* @brief Link time assert. If linker fails to link with it, it means that fake_object used in run time.
*/
template <class T>
extern const wrapper<T> DO_NOT_USE_REFLECTION_WITH_LOCAL_TYPES;
/**
* @brief For returning non-default constructible types.
* @tparam T
* @details
* Neither std::declval nor unsafe_declval are suitable here.
*/
template <class T>
constexpr const T &fake_object() noexcept {
return DO_NOT_USE_REFLECTION_WITH_LOCAL_TYPES<T>.value;
}
template <
class UniqueKey, // https://developercommunity.visualstudio.com/t/__FUNCSIG__-outputs-wrong-value-with-C/10458554
auto M>
consteval std::string_view name_of_field_impl() noexcept {
std::string_view s = __FUNCSIG__;
s = s.substr(s.rfind("->value->"));
s = s.substr(sizeof("->value->") - 1);
s = s.substr(0, s.rfind(">(void)"));
return s;
}
template <
class UniqueKey, // https://developercommunity.visualstudio.com/t/__FUNCSIG__-outputs-wrong-value-with-C/10458554
auto M>
consteval std::string_view name_of_field_impl_method() noexcept {
std::string_view s = __FUNCSIG__;
s = s.substr(s.rfind(':') + 1);
s = s.substr(0, s.find('('));
return s;
}
#endif
template<typename Clazz, auto M>
consteval std::string_view field_name() {
#if AUI_COMPILER_MSVC
constexpr bool isMemberField = requires (Clazz&& z) { std::addressof(z.*M); };
if constexpr (isMemberField) {
return detail::name_of_field_impl<
Clazz,
std::addressof(std::addressof(detail::fake_object<Clazz>())->*M)>();
} else {
return detail::name_of_field_impl_method<Clazz, M>();
}
#elif AUI_COMPILER_CLANG
std::string_view s = __PRETTY_FUNCTION__;
{
auto last = s.rfind(']');
auto begin = s.rfind('&');
s = s.substr(begin, last - begin);
}
if (auto c = s.rfind(':')) {
s = s.substr(c + 1);
}
return s;
#else
std::string_view s = __PRETTY_FUNCTION__;
{
auto last = s.rfind(';');
auto begin = s.rfind('&');
s = s.substr(begin, last - begin);
}
if (auto c = s.rfind(':')) {
s = s.substr(c + 1);
}
return s;
#endif
}
} // namespace detail
#endif
/**
* @brief Pointer to member value (not type) introspection.
* @ingroup reflection
* @details
* In comparison to @ref aui::reflect::member, aui::reflect::member_v inspects pointer-to-member value, instead of type.
* Since value points to exact member instead of generalizing by type, this allows to introspect additional data such as
* member name:
*
* @snippet aui.core/src/AUI/Reflect/members.h getName
*
* # Example
*
* @snippet aui.core/tests/ReflectTest.cpp member_v
* @snippet aui.core/tests/ReflectTest.cpp member_v2
*
* # Derived data from aui::reflect::member
* aui::reflect::member_v derives members from @ref aui::reflect::member.
*
* @copydetails aui::reflect::member
*/
template <auto M>
struct member_v : member<decltype(M)> {
#if AUI_REFLECT_FIELD_NAMES_ENABLED
/// [getName]
/*
* @brief Field name.
* @details
* Compile-time `std::string_view` that holds name of the field.
*
* It's implemented via forbidden compiler-specific magic and requires your `class`/`struct` to be defined with
* external linkage, i.e., please do not use function local types.
*/
static constexpr std::string_view name = detail::field_name<typename member_v::clazz, M>();
/// [getName]
#endif
};
//static_assert(member_v<&std::string_view::data>::name == "data");