使用std::ranges/std::views和C++23中预期的std::的最佳方法是什么?

6ie5vjzr  于 2023-02-26  发布在  其他
关注(0)|答案(2)|浏览(188)

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

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

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

enum class Error
{
    ValueError
};

然后,我们执行某种基于视图的处理,该处理在操作中使用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; 
   });

所以,在这个运算结束时,我们可以Assert

static_assert(
    std::is_same_v<
        std::decay_t<std::ranges::range_value_t<result>>, 
        std::expected<int, Error>
    >
)

都是真的。
问题是,然后呢?我们知道有一个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(); });
    }
}

但是接下来我们遇到了一个问题,函数不能推导出返回类型为std::expected<T, Error>,其中T是一个容器的类型,该容器包含类型为int的元素(在上面的例子中),我甚至不知道在这里为T写什么,所以我的问题是应该如何实现它?
神箭:https://godbolt.org/z/Wfjr8o3qM
或者,我感兴趣的是听听其他人如何一起以更好的方式处理这个问题?
谢谢
编辑:我想,你并不真的想返回一些元素的视图,因为这可能会导致一个悬空视图?在这种情况下,当从函数返回时,最好只使用ranges::to<T>()吗?

pgvzfuti

pgvzfuti1#

为了确定函数f的适当返回类型,可以使用std::ranges::range_value_tstd::ranges::range_reference_t类型特征,以及std::conditional_t类型特征。
首先,可以使用std::ranges::range_value_t<decltype(c)>获取基于视图的处理返回的范围的值类型,应该是std::expected<int, Error>
然后,您可以使用std::ranges::range_reference_t<decltype(c)>来获取范围的引用类型,如果范围是可变的,则应为std::expected<int, Error>&,如果范围是常量,则应为const std::expected<int, Error>&
最后,你可以使用std::conditional_t来有条件地选择返回类型,这是一个f的示例实现,它利用了这些类型特征:

template <typename Range>
auto f(Range&& range)
    -> std::conditional_t<
        std::ranges::any_of(range, [](auto&& e) { return !e.has_value(); }),
        std::ranges::range_value_t<decltype(range)>::error_type,
        std::vector<std::ranges::range_value_t<decltype(range)>::value_type>
    >
{
    using value_type = std::ranges::range_value_t<decltype(range)>::value_type;
    using error_type = std::ranges::range_value_t<decltype(range)>::error_type;
    
    auto c = std::forward<Range>(range)
        | views::filter([](int i){ return i % 2; })
        | views::transform([](int i) -> std::expected<value_type, 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<value_type>); v != c.end()) 
    {
        return (*v).error();
    }
    else 
    {
        return c | views::transform([](auto&& e){ return e.value(); })
                 | ranges::to<std::vector<value_type>>();
    }
}

在此实现中,根据范围内是否存在错误来选择返回类型,使用std::ranges::any_of。如果存在错误,则返回类型为error_type(相当于std::expected<value_type, Error>::error_type)。否则,返回类型是成功类型(value_type)的std::vector。请注意,views::transform operation用于在转换为std::vector之前将std::expected值的范围转换为成功值的范围。

jv4diomz

jv4diomz2#

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

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);
    }
}

它利用了这样一个事实,即视图的值是惰性计算的,因此如果它没有返回,值将永远不会被计算,我们可以使用它来确定返回类型。下一步是确保所有返回都被 Package 到ret_t类型中,以便auto可以正确猜测。
注意:无论是在这个答案还是你原来的问题中,这个只能在原来的范围内工作,可以迭代多次(不记得这个概念的名字了对不起)

相关问题