为何用hana::always包装按值返回函数可满足ranges::views::join的要求?
首先纠正你的一个认知偏差:第一段代码编译失败和运行时临时对象销毁没有任何关系,是编译阶段就不满足std::ranges::join的约束要求,根本不会生成运行时代码。
std::ranges::join的核心约束
根据C++20标准,std::ranges::join对输入范围(也就是这里transform返回的视图)有如下强制要求:
输入范围的元素引用类型range_reference_t<R>必须满足以下两个条件之一:
- 是引用类型;
- 不是引用类型时,元素的值类型必须是
view。
std::vector本身不属于view类型,所以只有当transform返回的元素是引用类型时,才能通过join的约束检查。
第一段代码编译失败的原因
你传给transform的make lambda返回的是纯右值std::vector<int>,因此transform视图的元素引用类型是std::vector<int>(非引用),既不满足第一条也不满足第二条,直接触发编译失败。
auto make = [](int){ return std::vector<int>{1,2,3}; }; // 编译失败 std::vector<int> v{1,2,3}; auto z = v | std::ranges::views::transform(make) | std::ranges::views::join;
用boost::hana::always后编译成功的原因
boost::hana::always(x)的作用是生成一个函数对象,它会把你传入的x作为内部成员存储,不管调用时传入什么参数,都返回内部存储的x。这里你传入的是make(int{}),意味着在构造always函数对象时,就已经生成了一个固定的std::vector<int>实例,它的生命周期和always函数对象完全绑定,而transform会把传入的函数对象存储在自身内部,和整个视图管道的生命周期保持一致。
重点在于boost::hana::always生成的函数对象有两个重载的调用运算符:
- 当用左值调用函数对象时,返回内部存储成员的
const&左值引用; - 当用右值调用函数对象时,返回内部存储成员的移动拷贝(纯右值)。
std::ranges::transform内部调用传入的函数对象时,函数对象是作为自身成员存在的左值,因此触发的是左值版本的调用运算符,返回的是const std::vector<int>&引用类型,完全满足join的约束要求,因此编译成功。
// 编译成功 std::vector<int> v{1,2,3}; auto z = v | std::ranges::views::transform(boost::hana::always(make(int{}))) | std::ranges::views::join;
你的静态断言存在的问题
static_assert(!std::is_reference_v<decltype(make(int{}))>); static_assert(!std::is_reference_v<decltype(boost::hana::always(make(int{}))(int{}))>);
你的静态断言测试的是临时always对象(右值)的调用结果,触发的是右值版本的调用运算符,返回的是纯右值,自然没有引用。这个测试场景和transform内部的左值调用场景完全不同,无法验证真实运行时的返回类型,因此结论不成立。
内容的提问来源于stack exchange,提问作者Enlico

