Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Statically iterate over all members of a C++ struct

Is there a way to statically iterate over all members of a C++ struct?

Say if we have many predefined structs which look like:

struct Foo {
    int field1;
    double field2;
    char field3;
    ...
    int field9;
};
struct Bar {
    double field14;
    char field15;
    int field16;
    bool field17;
    ...
    double field23;
};

And we want to have a template function

template<typename T>
void Iterate(T object);

so that Iterate can run a template function Add over all the members of type T. For example, Iterate<Foo> and Iterate<Bar> would become

void Iterate<Foo>(Foo object) {
    Add<int>(object.field1);
    Add<double>(object.field2);
    Add<char>(object.field3);
    ...
    Add<int>(object.field9);
}
void Iterate<Bar>(Bar object) {
    Add<double>(object.field14);
    Add<char>(object.field15);
    Add<int>(object.field16);
    Add<bool>(object.field17);
    ...
    Add<double>(object.field23);
}

This can be done by writing another program that parses the struct definition and generate a cpp file, but that would be too cumbersome and requires additional compiling and execution.

Edit: The struct may have many fields, and they are predefined, so it can't be changed into other types. Also this is at compile-time, so it has less to do with "reflection", which is performed at run-time, and more to do with "template programming" or "metaprogramming". We have <type_traits> for type inspection at compile-time, but that does not seem to be enough.

like image 493
WiSaGaN Avatar asked Dec 29 '14 10:12

WiSaGaN


2 Answers

There is no clear standard way to do such thing, but you can look at non-standard way. For example you can use boost::fusion.

BOOST_FUSION_ADAPT_STRUCT(
   Foo,
   (int, field1)
   (double, field2)
   (char, field3)
);

adapt struct After that, you can use objects of type Foo as fusion-sequence, which can be iterated by member. small live example

like image 165
ForEveR Avatar answered Nov 07 '22 09:11

ForEveR


I don't see any way to do this with a normal struct without retaining compile time information for the mapping of indices to types - at which point you create another std::tuple class. Let's give this a try:

#include <iostream>
#include <tuple>
#include <typeinfo>

template <size_t Cur, size_t Last, class TupleType, template <typename> class Func>
struct Iterate_Helper
{
    void operator()(TupleType& tuple)
    {
        typedef typename std::tuple_element<Cur, TupleType>::type elem_type;
        Func<elem_type>()(std::get<Cur>(tuple));
        Iterate_Helper<Cur+1, Last, TupleType, Func>()(tuple);
    }
};

template <size_t Cur, class TupleType, template <typename> class Func>
struct Iterate_Helper<Cur, Cur, TupleType, Func>
{
    void operator()(TupleType& tuple)
    {
        typedef typename std::tuple_element<Cur, TupleType>::type elem_type;
        Func<elem_type>()(std::get<Cur>(tuple));
    }
};

template <template <typename> class Func, class TupleType>
void iterate(TupleType& tuple)
{
    Iterate_Helper<0, std::tuple_size<TupleType>::value-1, TupleType, Func>()(tuple);
}

template <typename T>
struct Add1
{
    void operator()(T& t)
    {
        t += 1;
    }
};

template <typename T>
struct Print
{
    void operator()(T& t)
    {
        std::cout << (int)t << std::endl;
    }
};

int main() {
    typedef std::tuple<int, double, char, /* ... */ int> Foo;
    Foo test(1, 2.0, 3, 4);
    iterate<Add1>(test);
    iterate<Print>(test);        
    return 0;
}

This is on top of my head but I hope it can give you some insight.

like image 2
Veritas Avatar answered Nov 07 '22 07:11

Veritas