C++23中如何结合std::ranges/std::views与std::expected?
处理含std::expected的Ranges视图:错误传播与类型转换问题
场景背景
我们有个返回结果有效性不确定的函数random_predicate(),还有用于错误处理的枚举类Error。用Ranges视图处理std::vector<int>后,得到的视图元素类型是std::expected<int, Error>。
核心需求与遇到的问题
想要把这个视图转换成两种结果:要么碰到错误就向上传播Error,要么得到全是int的成功视图。
我试过先检查视图里有没有错误元素,有就返回错误,没有就转成int视图,但遇到两个问题:
- 函数返回类型没法推导成
std::expected<T, Error>,而且不知道T该设成啥; - 不确定返回视图会不会导致悬空,是不是该用
ranges::to<T>()返回容器?
一、解决返回类型推导问题
首先得明确:如果所有元素都成功,你要返回的是int类型的视图,那std::expected的成功类型就得匹配这个视图类型。要是原视图是std::ranges::view,T就是对应的视图类型,比如decltype(原视图 | std::views::transform([](auto&& e){ return e.value(); })),但直接写太麻烦,试试下面的实现:
#include <expected> #include <ranges> #include <vector> enum class Error { SomeError, AnotherError }; // 示例函数:返回预期值或错误 std::expected<int, Error> random_predicate(int val); template <std::ranges::input_range R> auto process_expected_view(R&& view) -> std::expected<decltype(view | std::views::transform([](auto&& e){ return e.value(); })), Error> { // 查找第一个错误元素 auto err_it = std::ranges::find_if(view, [](const auto& exp) { return !exp.has_value(); }); if (err_it != std::ranges::end(view)) { return std::unexpected(err_it->error()); } // 所有元素都有效,返回转换后的int视图 return view | std::views::transform([](auto&& exp) { return exp.value(); }); }
要是嫌返回类型太长,也可以用C++20的auto返回类型,让编译器自动推导:
template <std::ranges::input_range R> auto process_expected_view(R&& view) { auto err_it = std::ranges::find_if(view, [](const auto& exp) { return !exp.has_value(); }); if (err_it != std::ranges::end(view)) { // 先构造错误的expected,让编译器推导返回类型 return std::expected<decltype(view | std::views::transform([](auto&& e){ return e.value(); })), Error>{ std::unexpected(err_it->error()) }; } return std::expected<decltype(view | std::views::transform([](auto&& e){ return e.value(); })), Error>{ view | std::views::transform([](auto&& exp) { return exp.value(); }) }; }
二、视图悬空风险与返回容器的选择
- 视图悬空原因:视图本身不存储数据,只是对原数据的引用。如果原容器(比如你处理的
std::vector<int>)是临时对象,或者函数返回后原容器会被销毁,那返回的视图就会指向已经释放的内存,触发未定义行为。 - 返回视图的安全场景:只有当原数据的生命周期比视图的使用周期长时才安全,比如原
std::vector<int>是全局变量,或者由调用者管理且能保证在视图使用期间不会被销毁。 - 返回容器的场景:如果没法保证原数据的生命周期,或者需要保存转换后的结果,就用
ranges::to<std::vector<int>>()把视图转成容器返回。这样返回的是独立的容器,完全不存在悬空问题,唯一的代价是内存拷贝。
返回容器的实现示例:
template <std::ranges::input_range R> auto process_expected_view(R&& view) -> std::expected<std::vector<int>, Error> { auto err_it = std::ranges::find_if(view, [](const auto& exp) { return !exp.has_value(); }); if (err_it != std::ranges::end(view)) { return std::unexpected(err_it->error()); } return view | std::views::transform([](auto&& exp) { return exp.value(); }) | std::ranges::to<std::vector<int>>(); }
三、额外提示
如果你的视图是基于临时容器生成的(比如直接传std::vector<int>{1,2,3} | ...),绝对不能返回视图,必须返回容器,否则调用者拿到的视图会立刻悬空。
内容的提问来源于stack exchange,提问作者Jonah F
相关产品推荐
相关产品推荐

