C++23中std::views::zip处理抽象基类引用视图时报错
问题背景
编译命令:g++-13 -std=c++2b test.cpp
编译器版本:
g++-13 (Debian 13-20230106-1) 13.0.0 20230106 (experimental) [master r13-5040-g53add162511]
测试代码:
#include <array> #include <ranges> auto main() -> int { struct B { virtual void foo() = 0; }; struct D: B { virtual void foo() {} }; auto ds = std::array<D, 10>{}; auto to_b = [](D const& d) -> B const& { return d; }; auto bs = ds | std::views::transform(to_b); for (auto& b: bs) {} // 正常运行 auto zip = std::views::zip(ds, bs); for (auto&& pair: zip) {} // 报错 return 0; }
错误现象与疑问
遍历std::views::zip(ds, bs)时编译报错,错误信息显示编译器试图实例化std::pair<main()::D, main()::B>,但抽象类B无法被声明为成员。使用者预期得到的是std::pair<D&, B&>,不清楚为何会触发抽象类B的实例化,不确定是遗漏了C标准的细节,还是libstdc存在实现问题。
问题分析与解答
这个错误完全符合C标准的规定,并非libstdc的实现问题,核心原因在于std::views::zip的类型推导规则:
视图的reference与value_type的映射
对于bs这个transform_view,其迭代器的reference类型是B const&,但根据C++标准中视图的value_type推导规则,当reference为引用类型时,value_type会被推导为被引用的底层类型——也就是B const,而非引用类型本身。zip_view的value_type推导逻辑
std::views::zip生成的zip_view,其value_type是由各个输入视图迭代器的value_type组成的pair(当仅两个视图时)。具体来说,zip_view的value_type定义为std::tuple<std::iter_value_t<It>...>,而std::iter_value_t会自动剥离迭代器reference类型中的引用,得到原始类型。
这就导致zip_view的value_type变成了std::pair<D, B const>,而抽象类B无法被实例化,因此编译报错。即使实际遍历迭代器时返回的是引用类型的pair,编译器在处理模板元编程逻辑时,仍会尝试实例化这个value_type,从而触发错误。可行的解决方案
要绕过这个问题,需要让transform的返回类型成为可实例化的包装类型,而非直接返回抽象基类引用。最常用的方法是使用std::reference_wrapper:#include <functional> // 需要引入头文件 auto to_b = [](D const& d) -> std::reference_wrapper<B const> { return d; };此时
bs的value_type变为std::reference_wrapper<B const>,zip_view的value_type就会是std::pair<D, std::reference_wrapper<B const>>,既不会涉及抽象类的实例化,遍历zip时也能通过get()或直接解引用获取到B const&。
内容的提问来源于stack exchange,提问作者underscore_d

