问题描述
假设您有一个具有共同基类的std::tuple
:
class MyBase { public: virtual int getVal() = 0; };
class MyFoo1: public MyBase { public: int getVal() override { return 101; } };
class MyFoo2: public MyBase { public: int getVal() override { return 202; } };
using MyTuple = std::tuple<MyFoo1,MyFoo2,MyFoo1>;
如何在运行时遍历元组的元素?通常的答案是不能,因为它们都具有不同的类型,但是在这里,我很高兴为静态类型MyBase*
。我希望这样的代码:
MyTuple t;
for (Base* b : iterate_tuple<MyBase>(t)) {
std::cout << "Got " << b->getVal() << "\n";
}
How can you iterate over the elements of an std::tuple?上有很多有用的想法,但是它们都包含了一些在模版模板代码中的每次迭代中运行的代码,而我希望将所有的模版模板代码捆绑到假设的{ {1}}功能,所以我的代码只是普通的iterate_tuple
循环。
解决方法
这是一个小的包装函数,该函数通过在运行时指定的索引获取元组值,该函数通过使用不同的模板参数递归调用自身来进行线性搜索。您将其返回类型指定为模板参数,并且该值将隐式转换为它。
for
然后可以在元组的索引中循环使用它:
template <class BaseT,class TupleT,size_t currentIndex = 0>
BaseT* getBasePtr(TupleT& t,size_t desiredIndex) {
if constexpr (currentIndex >= std::tuple_size<TupleT>::value) {
return nullptr;
}
else {
if (desiredIndex == currentIndex) {
return &std::get<currentIndex>(t);
}
else {
return getBasePtr<BaseT,TupleT,currentIndex + 1>(t,desiredIndex);
}
}
}
它不像基于范围的for循环那么整洁,但是使用起来仍然非常简单。 (您可以将其包装在支持基于范围的循环的迭代器类中,但我并不认为这样做值得。)
,在与使用std::apply
相关的问题中提到并建议,这是获取元组的每个单独元素的好方法。
使用小的辅助函数来包装每个元组元素的转发使其易于使用。
这不是您要求的特定for循环语法,但是如果您问我,则很容易遵循。
#include <tuple>
#include <utility>
#include <iostream>
class MyBase { public: virtual int getVal() = 0; };
class MyFoo1: public MyBase { public: int getVal() override { return 101; } };
class MyFoo2: public MyBase { public: int getVal() override { return 202; } };
using MyTuple = std::tuple<MyFoo1,MyFoo2,MyFoo1>;
template <typename Tuple,typename Callable>
void iterate_tuple(Tuple&& t,Callable c) {
std::apply([&](auto&&... args){ (c(args),...); },t);
}
int main() {
MyTuple t;
iterate_tuple(t,[](auto& arg) {
std::cout << "Got " << arg.getVal() << "\n";
});
iterate_tuple(t,[](MyBase& arg) {
std::cout << "Got " << arg.getVal() << "\n";
});
}
我们可以使用auto
或使用通用基本类型来获取确切的类型。
正如Sam在评论中所建议的那样,从元组创建数组非常简单。
template<typename Base,typename Tuple,size_t... Is>
std::array<Base *,std::tuple_size_v<Tuple>> iterate_tuple_impl(Tuple& tuple,std::index_sequence<Is...>)
{
return { std::addressof(std::get<Is>(tuple))... };
}
template<typename Base,typename Tuple>
std::array<Base *,std::tuple_size_v<Tuple>> iterate_tuple(Tuple& tuple)
{
return iterate_tuple_impl(tuple,std::make_index_sequence<std::tuple_size_v<Tuple>>{});
}
,
如果您具有继承功能,为什么不没有元组就使用如下继承功能:
#include <iostream>
#include <vector>
class MyBase { public: virtual int getVal() = 0; };
class MyFoo1 : public MyBase { public: int getVal() override { return 101; } };
class MyFoo2 : public MyBase { public: int getVal() override { return 202; } };
int main() {
std::vector<std::unique_ptr<MyBase>> base;
base.emplace_back(new MyFoo1);
base.emplace_back(new MyFoo2);
for (auto && derived : base) {
std::cout << derived->getVal() << std::endl;
}
}
,
我将直接使用std::apply
,但是您可以创建Base*
的数组:
template <typename Base,typename Tuple>
std::array<Base*,std::tuple_size<Tuple>> toPtrArray(Tuple& tuple)
{
return std::apply([](auto& ... args){ return std::array<Base*,std::tuple_size<Tuple>>{{&args}}; },tuple);
}
然后
MyTuple t;
for (Base* b : toPtrArray<MyBase>(t)) {
std::cout << "Got " << b->getVal() << "\n";
}