T231104 异构类型迭代法
这篇稍微总结一下异构类型的迭代方法。
首先是静态异构类型,主要指的就是 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::variant
和 std::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。