在 C++23 中将 std::ranges/std::views 与 std::expected 一起使用的最佳方法是什么?

Jon*_*h F 7 c++ c++20 c++23

让我描述一个场景,首先我们有一个函数,它返回一些我们无法确定其有效性的数据,即

auto random_predicate() -> bool
{
    int v = uniform_dist(e1); // uniform distribution 1 to 100
    return (v % 5);
}
Run Code Online (Sandbox Code Playgroud)

其中uniform_dist()是适当种子的均匀分布,我们有一个enum class将用于错误处理的,即

enum class Error
{
    ValueError
};
Run Code Online (Sandbox Code Playgroud)

然后我们执行某种基于视图的处理,该处理random_predicate()在操作中使用,如下所示:

std::vector<int> vs{1,2,3,4,5};

auto result = vs
   | views::filter([](int i){ return i % 2; })
   | views::transform([](int i) -> std::expected<int, Error> {
      auto v = random_predicate();
      if (v) return std::unexpected<Error>(Error::ValueError);
      else return i * i; 
   });
Run Code Online (Sandbox Code Playgroud)

因此,在该操作结束时,我们可以断言

static_assert(
    std::is_same_v<
        std::decay_t<std::ranges::range_value_t<result>>, 
        std::expected<int, Error>
    >
)
Run Code Online (Sandbox Code Playgroud)

事实上将会是真的。

问题是,然后呢?我们知道有一个需要解析为的值视图std::expected:向上传播调用堆栈的错误类型,或成功类型的视图(即上面示例中的视图int(所有元素不是 5 的倍数) !))


我的解决方案

我的解决方案是简单地检查每个元素是否有错误,然后如果没有错误,则将结果转换为所需的视图,所以类似

template<typename T>
static auto has_error(const std::expected<T, Error>& e){ return !e.has_value(); };

auto f(const std::vector<int>& vs)
{    
    auto c = vs
        | views::filter([](int i){ return i % 2; })
        | views::transform([](int i) -> std::expected<int, Error> {
            auto v = random_predicate();
            if (v) return std::unexpected<Error>(Error::ValueError);
            else return i * i; 
        });

    if (auto v = ranges::find_if(c, has_error<int>); v != c.end()) 
    {
        return (*v).error();
    }
    else 
    {
        return c | views::transform([](auto&& e){ return e.value(); });
    }
}
Run Code Online (Sandbox Code Playgroud)

但随后我们遇到了一个问题,即该函数无法将返回类型推断为std::expected<T, Error>具有T类型元素的容器的类型(在上面的示例中)int。好吧,我什至不知道在这里要写什么T,所以我的问题是应该如何实现?

神螺栓: https: //godbolt.org/z/Wfjr8o3qM


或者,我有兴趣听听其他人如何以更好的方式共同解决这个问题?

谢谢

编辑:我想,您真的不想返回某些元素的视图,因为它可能会导致悬空视图?在这种情况下,最好只ranges::to<T>()在从函数返回时使用吗?

Ale*_*iet 4

仍然能够返回范围的另一个选项:

auto f(const std::vector<int> &vs) {
    auto c = vs
             | views::filter([](int i) { return i % 2; })
             | views::transform([](int i) -> std::expected<int, Error> {
        auto v = random_predicate();
        if (v) return std::unexpected<Error>(Error::ValueError);
        else return i * i;
    });

    auto values = c | views::transform([](auto &&e) { return e.value(); });

    using success_t = decltype(values);
    using ret_t = std::expected<success_t, Error>;

    if (auto v = ranges::find_if(c, has_error<int>); v != c.end()) {
        return ret_t(std::unexpected<Error>((*v).error()));
    } else {
        return ret_t(values);
    }
}
Run Code Online (Sandbox Code Playgroud)

它利用了这样一个事实:值作为视图是延迟计算的。因此,如果不返回,则永远不会计算值,我们可以使用它来确定返回类型。下一步是确保所有返回都包装到ret_t类型中,以便 auto 可以正确猜测。

注意:在这个答案和您原来的问题中,这只适用于原始范围,可以多次迭代(抱歉不记得这个概念的名称)

  • “*在这个答案和您原来的问题中,这只适用于可以多次迭代的原始范围*”即`ranges::forward_range`。 (2认同)