aui icon indicating copy to clipboard operation
aui copied to clipboard

Reflection

Open Alex2772 opened this issue 1 year ago • 2 comments

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.

Alex2772 avatar Jan 17 '25 05:01 Alex2772

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);
}

Alex2772 avatar Feb 27 '25 17:02 Alex2772


#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");

Alex2772 avatar Feb 27 '25 21:02 Alex2772