1

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

Fabian
  • 152
  • 2
  • 14
  • 1
    "*I can't wait for c++20 with actual reflection support*" You realize that the reflection proposal hasn't actually been approved yet, right? It likely won't make it into C++20. – Nicol Bolas Oct 11 '17 at 00:34
  • 1
    @NicolBolas bad news. – Tomilov Anatoliy Oct 11 '17 at 05:26
  • @Orient i got the code to compile, but now aggregate_arity returns different values for the same type, how is that possible? Nicol Bolas, oh no thats bad :( – Fabian Oct 11 '17 at 21:20
  • Not an answer to your problem but probably highly related: There exists an early-stage [library for reflection without macros](https://github.com/apolukhin/magic_get) with techniques explained in a [conference talk recording](https://www.youtube.com/watch?v=abdeAew3gmQ). – Julius Oct 12 '17 at 12:34
  • @Julius thanks for the link, i checked it out: the pre boost version only seems to work with flattend structs. the boost version (if i include pfr.hpp) does not compile on the lastest msvc compiler version. if i just use boost\pfr\precise\tuple_size.hpp header for `constexpr size_t M = boost::pfr::tuple_size_v;` i get this nice message: "error C2338: Something went wrong. Please report this issue to the github along with the structure you're reflecting." – Fabian Oct 13 '17 at 11:16

1 Answers1

0

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

Fabian
  • 152
  • 2
  • 14