为何std::set<std::unique_ptr<int>>结合std::views::as_rvalue转换失败?
std::set<std::unique_ptr<int>>结合as_rvalue与to<vector>编译失败分析 我编写了一段代码,其中仅最后一个代码块无法运行。原以为这与std::unique_ptr仅可移动或std::set的键为const有关,但无法理解为何其他代码块可正常运行。
代码示例
namespace sr = std::ranges; namespace sv = std::views; int main() { { std::set<int> up_s{10,20}; const auto up_vec = sv::as_rvalue(up_s) | sr::to<std::vector>(); assert(up_vec.size() == 2); } { std::vector<std::unique_ptr<int>> up_d; up_d.emplace_back(std::make_unique<int>(10)); up_d.emplace_back(std::make_unique<int>(20)); const auto up_vec = sv::as_rvalue(up_d) | sr::to<std::vector>(); assert(up_vec.size() == 2); } { std::set<std::unique_ptr<int>> up_d; up_d.emplace(std::make_unique<int>(10)); up_d.emplace(std::make_unique<int>(20)); // 取消注释后编译失败 // const auto up_vec = sv::as_rvalue(up_d) | sr::to<std::vector>(); // assert(up_vec.size() == 2); } }
编译错误信息
/opt/compiler-explorer/gcc-trunk-20240513/include/c++/15.0.0/ranges:9354:25: 错误:静态断言失败
9354 | static_assert(input_range<range_reference_t<_Rg>>);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-trunk-20240513/include/c++/15.0.0/ranges:9354:25: 注意:约束未满足
...
/opt/compiler-explorer/gcc-trunk-20240513/include/c++/15.0.0/bits/ranges_base.h:499:13:
要求满足 'range<_Tp>' [其中 _Tp = const std::unique_ptr<int, std::default_delete>&&]
/opt/compiler-explorer/gcc-trunk-20240513/include/c++/15.0.0/bits/ranges_base.h:499:21:
在要求中使用 '_Tp& __t' [其中 _Tp = const std::unique_ptr<int, std::default_delete>&&]
/opt/compiler-explorer/gcc-trunk-20240513/include/c++/15.0.0/bits/ranges_base.h:501:22:
注意:所需表达式 'std::ranges::_Cpo::begin(__t)' 无效
501 | ranges::begin(__t);
| ~~~~~~~~~^
/opt/compiler-explorer/gcc-trunk-20240513/include/c++/15.0.0/bits/ranges_base.h:502:20:
注意:所需表达式 'std::ranges::_Cpo::end(__t)' 无效
502 | ranges::end(__t);
原因分析
核心问题出在GCC对std::ranges::to的模板推导逻辑上:
std::set的元素天生是const的,因此sv::as_rvalue(up_d)生成的range元素类型是const std::unique_ptr<int>&&。- 当使用
sr::to<std::vector>()(未显式指定元素类型)时,编译器的模板推导错误地将该元素类型当成了range类型,尝试对const std::unique_ptr<int>&&调用begin()和end(),触发了input_range<range_reference_t<_Rg>>的静态断言——而unique_ptr显然不是range,因此断言失败。
对比前两个可正常运行的案例:
std::set<int>案例:元素类型是const int&&,int不是range,编译器不会误判,可正常移动构造vector<int>。std::vector<std::unique_ptr<int>>案例:vector的元素非const,as_rvalue后元素类型是std::unique_ptr<int>&&,同样不会被误判为range,可正常移动构造vector<std::unique_ptr<int>>。
解决方案
只需显式指定std::ranges::to的目标容器元素类型,避免模板推导歧义:
const auto up_vec = sv::as_rvalue(up_d) | sr::to<std::vector<std::unique_ptr<int>>>();
或者通过std::views::transform显式移动元素,让推导逻辑更清晰:
const auto up_vec = sv::as_rvalue(up_d) | sv::transform([](auto&& ptr) { return std::move(ptr); }) | sr::to<std::vector>();
内容的提问来源于stack exchange,提问作者NoSenseEtAl

