【问题标题】:Statically iterate over all members of a C++ struct静态迭代 C++ 结构的所有成员
【发布时间】:2014-12-29 10:52:47
【问题描述】:

有没有办法静态迭代 C++ 结构的所有成员?

假设我们有很多预定义的结构,看起来像:

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

我们想要一个模板函数

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

这样Iterate 可以对T 类型的所有成员运行模板函数Add。例如,Iterate&lt;Foo&gt;Iterate&lt;Bar&gt; 将变为

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

这可以通过编写另一个程序来解析struct定义并生成cpp文件来完成,但这太麻烦了,需要额外的编译和执行。

编辑:结构体可能有很多字段,并且它们是预定义的,因此不能更改为其他类型。这也是在编译时,所以它与在运行时执行的“反射”关系不大,更多的是与“模板编程”或“元编程”有关。我们有 &lt;type_traits&gt; 在编译时进行类型检查,但这似乎还不够。

【问题讨论】:

  • 听起来你想要一个std::tuple。我尝试了类似的 here 并在生产中使用了解决方案。
  • 对我来说这是一个基本的设计方面。可能使用异构容器。
  • c++ 没有反射。
  • 你看过Boost序列化库吗?

标签: c++ metaprogramming


【解决方案1】:

没有明确的标准方式来做这样的事情,但你可以看看非标准的方式。例如,您可以使用boost::fusion

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

adapt struct 之后,您可以使用Foo 类型的对象作为融合序列,可以通过成员进行迭代。 small live example

【讨论】:

  • 看起来很有趣。但它仍然需要用户重新定义结构。
  • @WiSaGaN 是的,但是您可以使用 BOOST_FUSION_DEFINE_STRUCT,它只会在您想要的命名空间中定义结构。
  • (我赞成这个答案,这只是一个评论)在反射被添加到标准之前,您需要手动提供额外的元数据;谁知道呢,也许将来 boost_adapt_struct 可能会使用反射;这是使用库而不是滚动您自己的库的一个论据...
【解决方案2】:

我看不出有任何方法可以在不保留索引到类型映射的编译时间信息的情况下使用普通结构执行此操作 - 此时您创建另一个 std::tuple 类。让我们试一试:

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

这是我的想法,但我希望它能给你一些见解。

【讨论】:

    【解决方案3】:

    是的,Boost.Fusion 和扩展机制很好地支持了这一点。

    您可以在此处找到 for_each_member 实现:https://github.com/cpp-pre/json/blob/master/pre/fusion/for_each_member.hpp

    我使用它来生成 JSON 并从 json 读取结构,如我在此处的其他回复中所述:https://stackoverflow.com/a/28580824/271781

    【讨论】:

    猜你喜欢
    • 2013-02-05
    • 2017-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-21
    相关资源
    最近更新 更多