如何使用共享基类遍历std :: tuple的元素?

问题描述

假设您有一个具有共同基类的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";
}

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...