这篇稍微总结一下异构类型的迭代方法。

首先是静态异构类型,主要指的就是 tuple like 类型,它们可以使用 std::apply 迭代。

void print(auto tuple_like_value) {
    std::apply([](const auto&... args) {
        (fmt::print("{} ", args), ...);
    }, tuple_like_value);

    fmt::print("\n");
}

int main() {

    // Iterate std::pair
    {
        using heterogenous_t = std::pair<int, double>;
        heterogenous_t container { 1, 0.8 };
        print(container);
    }

    // Iterate std::tuple
    {
        using heterogenous_t = std::tuple<int, double, bool>;
        heterogenous_t container { 1, 0.42, true };
        print(container);
    }

}

接着是动态异构类型,主要指的是 std::variantstd::any

对于 std::variant,标准提供 std::visit 进行迭代。

using value_type = std::variant<int, double, std::string>;
using heterogenous_t = std::vector<value_type>;
heterogenous_t container;
container.push_back(5);
container.push_back(0.42);
container.push_back("hello");
std::ranges::for_each(container, [](const value_type& value) {
    std::visit([](const auto& x){ fmt::print("{} ", x); }, value);
});

对于 std::any,由于它使用了类型擦除,标准并未提供迭代函数,得通过以下方式逐个遍历:

using value_type = std::any;
using heterogenous_t = std::vector<value_type>;
heterogenous_t container;
container.push_back(5);
container.push_back(0.42);
container.push_back("hello");
for (const auto& a : container) {
    if (a.type() == typeid(int)) {
        const auto& value = std::any_cast<int>(a);
        fmt::print("{} ", value);
    } else if (a.type() == typeid(const char*)) {
        const auto& value = std::any_cast<const char*>(a);
        fmt::print("{} ", value);
    } else if (a.type() == typeid(bool)) {
        const auto& value = std::any_cast<bool>(a);
        fmt::print("{} ", value);
    } else if (a.type() == typeid(double)) {
        const auto& value = std::any_cast<double>(a);
        fmt::print("{} ", value);
    }
}

大量的重复!既然前段时间写了不少宏产生式元编程技术,这里展示一下如何借助宏来消除这种重复。

根据《产生式元编程》 第二章 自复用代码生成技中展示的技术,可以实现如下组件:

#define _FOR_EACH_ANY_0(call, ...)
#define _FOR_EACH_ANY_1(call, cp, a, x) call(cp, a, x, dummy)
#define _FOR_EACH_ANY_2(call, cp, a, x, ...) call(cp, a, x) _FOR_EACH_ANY_1(call, cp, a, __VA_ARGS__)
#define _FOR_EACH_ANY_3(call, cp, a, x, ...) call(cp, a, x) _FOR_EACH_ANY_2(call, cp, a, __VA_ARGS__)
#define _FOR_EACH_ANY_4(call, cp, a, x, ...) call(cp, a, x) _FOR_EACH_ANY_3(call, cp, a, __VA_ARGS__)
#define _FOR_EACH_ANY_5(call, cp, a, x, ...) call(cp, a, x) _FOR_EACH_ANY_4(call, cp, a, __VA_ARGS__)

#define FOR_EACH_ANY(call, cp, a, ...) \
    EXPAND( OVERLOAD_INVOKE(_FOR_EACH_ANY, COUNT_VARARGS(__VA_ARGS__))(call, cp, a, __VA_ARGS__) )

#define ANY_VISITOR_END_1()
#define ANY_VISITOR_END_0() else 
#define ANY_VISITOR_END(flag) OVERLOAD_INVOKE(ANY_VISITOR_END, flag)()
#define ANY_VISITOR(cp, a, x, ...) \
    if(a.type() == typeid(x)) { \
        const auto& value = std::any_cast<x>(a); \
        std::invoke(cp, value); \
    } ANY_VISITOR_END(COUNT_VARARGS(__VA_ARGS__))

#define ANY_VISIT(cp, a, ...) EXPAND( FOR_EACH_ANY(ANY_VISITOR, cp, a, __VA_ARGS__) )

利用实现的组件,便可以像下面这样轻易地迭代 std::any 构成的容器:

#define TYPES int, const char*, double, std::string, bool

std::ranges::for_each(container, [](const auto& a) {
    ANY_VISIT([](const auto& x) { fmt::print("{} ", x); }, a, TYPES)
});

当然你也可以尝试用模板来消除此类重复,实现一个 any_visit<types>(...) 函数,感兴趣的可以试一试。

cppref 也提供了一种查表的迭代实现方式,见这里

而对于结构体的迭代,虽然缺乏反射机制,但也可以借助 magic_get 中的技术来实现。这里展示一下成品的使用:

struct S {
    int a;
    double b;
    char c;
};

int main()
{
    S s { 1, 0.52, 'c' };
    boost::pfr::for_each_field(s, [](auto&& x){
        std::cout << x << ' ';
    });
}

boost::pfr 就是加入 boost 当中使用 magic_get 技术实现的库,之前也提过,原理是 Structured bindings。

Leave a Reply

Your email address will not be published. Required fields are marked *

You can use the Markdown in the comment form.