How to enumerate all member variables of a class /

2019-07-28 11:42发布

问题:

I'm working on some kind of simple reflection for c++ structs where i want to recursivly iterate over all member variables. The code below almost does what i want but my compiler complians: "recursive type or function dependency context too complex" coming form aggregate_arity<MemberType>::size() which is based on Orients aggregate_arity implementation.

Example usage case:

struct B
{
    SPVStruct;
    var_t<float2_t, true> f4;
};

struct A
{
    SPVStruct;
    var_t<float2_t, true> f2;
    var_t<float3_t, true> f3;
    float d;

    B b;
};

A a{};
InitializeStruct<A, true>(a);

Implementation:

struct TSPVStructTag {};

#ifndef SPVStruct
#define SPVStruct typedef TSPVStructTag SPVStructTag;
#endif

    template< class, class = std::void_t<> >
    struct has_spv_tag : std::false_type { };

    template< class T >
    struct has_spv_tag<T, std::void_t<typename T::SPVStructTag>> : std::true_type { };

    template <class T>
    void InitVar(T& _Member) {}

    template <class T, bool Assemble>
    void InitVar(var_t<T, Assemble>& _Member)
    {
        // actual stuff happening here
    }

    template <size_t N, class T, bool Assemble>
    void InitStruct(T& _Struct)
    {
        if constexpr(N > 0u)
        {
            auto& member = get<N-1>(_Struct);
            using MemberType = typename std::decay_t<decltype(member)>;
            if constexpr(has_spv_tag<MemberType>::value)
            {
                constexpr size_t n = aggregate_arity<MemberType>::size(); // this is the complex recursion that blows up
                InitStruct<n, MemberType, Assemble>(member);                
            }
            else
            {
                InitVar(member);
                InitStruct<N - 1, T, Assemble>(_Struct);
            }
        }
    }

    template <class T, bool Assemble>
    void InitializeStruct(T& _Struct)
    {
        constexpr size_t N = aggregate_arity<T>::size();
        InitStruct<N, T, Assemble>(_Struct);
    }

Example

I use the has_spv_tag to mark structs that should be reflected. I can't wait for c++20 with actual reflection support :(

Thanks for your help!

Edit: I got it to compile and changed the iteration order. Now a different problem comes up: constexpr size_t M = aggregate_arity::size() returns 0 even for the same type it returned the correct value earlier. i verified that the type is infact the same (first struct type B) by comparing the hash from typeid. How is it possible to that aggregate returns two different values for the exact same type?

    template <class T, bool Assemble>
    constexpr bool is_var_t(var_t<T, Assemble>& _Member) { return true; }

    template <class T>
    constexpr bool is_var_t(T& _Member) { return false; }

    template <class T>
    void InitVar(T& _Member) { std::cout << typeid(T).name() << std::endl; }

    template <class T, bool Assemble>
    void InitVar(var_t<T, Assemble>& _Member)
    {
        // actual stuff happening here
        std::cout << typeid(T).name() << std::endl;
    }

    template <size_t n, size_t N, class T>
    void InitStruct(T& _Struct)
    {
        std::cout << "n " << n << " N " << N << std::endl;
        if constexpr(n < N)
        {
            decltype(auto) member = get<n>(_Struct);
            using MemberType = std::remove_cv_t<decltype(member)>;
            std::cout << typeid(MemberType).hash_code() << std::endl;

            if (is_var_t(member))
            {
                InitVar(member);
                InitStruct<n + 1, N, T>(_Struct);
            }
            else
            {
                constexpr size_t M = aggregate_arity<MemberType>::size();
                InitStruct<0, M, MemberType>(member);
            }
        }
    }

Edit 2: example for the new version: http://coliru.stacked-crooked.com/a/b25a84454d53d8de

回答1:

Antony Polukhin pointed out the problem: MemberType still had the reference from get(_Struct). The code works with

MemberType = std::remove_reference_t<std::remove_cv_t<decltype(member)>>;

template <size_t n, size_t N, class T>
void InitStruct(T& _Struct)
{
    if constexpr(n < N)
    {
        decltype(auto) member = get<n>(_Struct);
        using MemberType = std::remove_reference_t<std::remove_cv_t<decltype(member)>>;

        if constexpr(has_spv_tag<MemberType>::value)
        {
            InitStruct<0, aggregate_arity<MemberType>::size(), MemberType>(member);
        }
        else
        {
            InitVar(member);
        }
        InitStruct<n + 1, N, T>(_Struct);
    }
}

I now use has_spv_tag<MemberType>::value to identify which member is a struct that i want to enumerate. There was also a bug with the order of InitStruct<n + 1, N, T>(_Struct);