为何无法通过const引用传递std::views::join?
核心原因
你通过std::views::join生成的视图不支持常量语境下的迭代。
标准库中的很多视图适配器(比如join、filter、flat_map)的迭代逻辑依赖视图内部的可变状态——比如join需要跟踪当前遍历的内层range迭代器,这些状态会在迭代过程中被修改。因此这类视图不满足std::ranges::constant_range概念(即无法在const引用上调用std::ranges::begin/end),而你的print2和equal2函数要求参数是const Range&,这就导致编译器找不到合法的begin/end重载。
而print1和equal1使用的是转发引用(Range&&),当你传入左值的view时,Range会被推导为视图类型的左值引用,调用begin/end时是对非const的视图对象操作,完全符合视图的迭代要求,因此可以正常运行。
解决GoogleTest匹配器的问题
你的MATCHER_P(RangeEq, value, "")中,GoogleTest默认会把arg以const&的形式传递给匹配逻辑,这就和equal2遇到了同样的问题:无法在const引用的join视图上调用std::ranges::equal。
你有两种解决方案:
方案1:让匹配器支持转发引用
修改匹配器模板,让它接受转发引用类型的参数,保留参数的const属性和值类别:
template <typename Range> MATCHER_P(RangeEq, value, "") { return std::ranges::equal(std::forward<Range>(arg), value); }
如果GoogleTest的MATCHER_P推导模板参数有问题,可以改用MATCHER_P_T来显式指定模板参数:
MATCHER_P_T(RangeEq, value, "") { return std::ranges::equal(std::forward<decltype(arg)>(arg), value); }
方案2:将视图转为支持常量迭代的容器
如果必须使用const引用的匹配逻辑,可以先把视图转换为容器(比如std::vector),容器天生支持常量迭代:
auto view = std::views::iota(0, 5) | std::views::transform([](auto num) { return std::views::iota(0, num); }) | std::views::join; std::vector<int> view_container(view.begin(), view.end()); EXPECT_THAT(view_container, RangeEq(nums));
补充说明
std::ranges::range概念只要求类型能在非const语境下被迭代,而std::ranges::constant_range才要求支持const语境下的迭代。大部分需要跟踪内部状态的适配器视图都不属于constant_range,因此在编写通用range处理函数时,除非明确需要支持常量迭代,否则应该优先使用转发引用,以兼容更多类型的range。
内容的提问来源于stack exchange,提问作者Hesky Fisher

