如何使用ranges-v3拼接不同类型vector遍历调用通用接口
问题本质
直接使用std::ranges::views::concat拼接存储不同元素类型的std::vector无法通过编译。ranges::views::concat的核心约束是:所有传入的输入range必须存在统一的公共引用类型,作为拼接后视图迭代器的解引用类型。std::vector<A>和std::vector<B>的元素是完全独立的自定义类型,不存在默认公共类型,因此循环中v的类型无法推导,编译直接报错。
可落地的实现方式
方式1:统一类型后拼接
如果一定要用concat实现单循环遍历,可以先将两个vector的元素转换为统一的公共类型,再做拼接:
- 若A、B继承自同一公共基类,可转换为基类引用/指针,通过虚函数实现多态调用:
struct Base { virtual void foo() const = 0; virtual ~Base() = default; }; struct A : public Base { void foo() const override { /* A对应逻辑 */ } }; struct B : public Base { void foo() const override { /* B对应逻辑 */ } }; int main() { std::vector<A> as(3); std::vector<B> bs(4); auto view_as = as | std::views::transform([](const A& a) -> const Base& { return a; }); auto view_bs = bs | std::views::transform([](const B& b) -> const Base& { return b; }); for (const auto& obj : std::views::concat(view_as, view_bs)) { obj.foo(); } }
- 若A、B无继承关系,可使用
std::variant包装两类元素,遍历阶段通过std::visit分发调用对应foo重载:
#include <variant> // 前置定义A、B以及void foo(const A&)、void foo(const B&) int main() { std::vector<A> as(3); std::vector<B> bs(4); using Elem = std::variant<A, B>; auto view_as = as | std::views::transform([](const A& a) -> Elem { return a; }); auto view_bs = bs | std::views::transform([](const B& b) -> Elem { return b; }); for (const auto& v : std::views::concat(view_as, view_bs)) { std::visit([](const auto& obj) { foo(obj); }, v); } }
方式2:封装多段遍历逻辑(零额外开销)
类型包装会带来一定的代码侵入性,最直接无额外开销的方式是分段遍历两个vector,逻辑和拼接遍历完全等价。如果不想写重复循环代码,可以封装可变参数模板统一处理:
template<typename Func, typename... Ranges> void traverse_all(Func&& f, Ranges&&... rs) { auto run = [&](auto&& r) { for (const auto& item : r) { std::invoke(std::forward<Func>(f), item); } }; (run(std::forward<Ranges>(rs)), ...); } // 调用示例 int main() { std::vector<A> as(3); std::vector<B> bs(4); traverse_all([](const auto& v) { foo(v); }, as, bs); }
这种实现不需要对A、B类型做任何修改,没有类型转换开销,支持传入任意多个不同元素类型的range,适配性更强。
内容的提问来源于stack exchange,提问作者jjcasmar
相关产品推荐
相关产品推荐

