C++20范围视图转换为向量

9
现在,C++20范围实现已经在GCC 10.2中发布,我想知道如何将一个范围视图转换回一个实际的容器,比如vector。我找到了这个问题(Range view to std::vector),它问的是预发布版本的同样问题,但是自从发布以来,是否有任何新的方法可以从视图转换为容器?或者那个问题中的单个答案仍然是最好的解决方案?

这个回答解决了你的问题吗?Range view to std::vector - Rupert Nash
3个回答

15

最简单的方法是使用range-v3,它恰好为此提供了一种转换运算符。来自示例

using namespace ranges;
auto vi =
    views::for_each(views::ints(1, 10), [](int i) {
        return yield_from(views::repeat_n(i, i));
    })
  | to<std::vector>();
// vi == {1,2,2,3,3,3,4,4,4,4,5,5,5,5,5,...}

否则,链接问题中的答案并不完全准确,因为范围可能没有相同的迭代器和终止器类型,而答案要求它们相同。因此,我们可以做得更好:

template <std::ranges::range R>
auto to_vector(R&& r) {
    std::vector<std::ranges::range_value_t<R>> v;

    // if we can get a size, reserve that much
    if constexpr (requires { std::ranges::size(r); }) {
        v.reserve(std::ranges::size(r));
    }

    // push all the elements
    for (auto&& e : r) {
        v.push_back(static_cast<decltype(e)&&>(e));
    }

    return v;
}

以上内容的简化版本,不一定在所有相同的位置上进行预留,通过使用views::common来解决混合哨兵类型问题:

一个不一定在所有相同位置上进行预订的以上内容的简化版本,通过使用views::common解决混合哨兵类型问题。
template <std::ranges::range R>
auto to_vector(R&& r) {
    auto r_common = r | std::views::common;
    return std::vector(r_common.begin(), r_common.end());
}

在这里错过了预留的典型例子将是使用具有O(1) size()std::list<T>调用 to_vector() 进行预留,但一旦进入迭代器,我们就会失去它。


1
谢谢您详细的回答,但是您是否知道为什么 range-v3 的 to 和 to_vector 函数被从 C++20 规范中删除了呢? - Foxie
@Foxie 他们并没有被删除 - 它们从未存在过。 - Barry
1
为什么它们从未出现呢?似乎有能力从视图返回到具体容器是一件好事。 - Foxie
1
@Foxie 这是一件好事。但是Ranges已经非常庞大了。还有很多好东西没有被包含进去。 - Barry
3
@Foxie C++23已经添加了std::ranges::to,所以你可以简单地使用yourVectorView | std::ranges::to<std::vector>();由于自动推导,std::vector的模板参数有时可以省略。 - o_oTurtle

4

使用Barry的答案并创建一个适配器:

/**
 * \brief Creates a to_vector_closure for operator()
 */
struct to_vector_adapter
{
    struct closure
    {
        /**
         * \brief Gets a vector of a given range.
         * \tparam R type of range that gets converted to a vector.
         * \param r range that gets converted to a vector.
         * \return vector from the given range.
         */
        template<std::ranges::range R>
        constexpr auto operator()(R&& r) const
        {
            auto r_common = r | std::views::common;
            std::vector<std::ranges::range_value_t<R>> v;

            // if we can get a size, reserve that much
            if constexpr (requires { std::ranges::size(r); }) {
                v.reserve(std::ranges::size(r));
            }

            v.insert(v.begin(), r_common.begin(), r_common.end());

            return v;
        }
    };

    /**
     * \brief Gets a closure to convert the range to a vector.
     * \return A to_vector_closure that will convert the range to a vector.
     */
    constexpr auto operator()() const -> closure
    {
        return closure{};
    }


    template<std::ranges::range R>
    constexpr auto operator()(R&& r)
    {
        return closure{}(r);
    }
};

inline to_vector_adapter to_vector;

/**
 * \brief A range pipe that results in a vector.
 * \tparam R type of range that gets converted to a vector.
 * \param r range that gets converted to a vector.
 * \param a used to create the vector.
 * \return a vector from the given range.
 */
template<std::ranges::range R>
constexpr auto operator|(R&& r, aplasp::planning::to_vector_adapter::closure const& a)
{
    return a(std::forward<R>(r));
}

使用这个功能,你可以像下面这样做(其中numbers是一个int范围):

std::vector foo
{
    numbers
    | std::views::filter([](int n){ return n % 2 == 0; })
    | std::views::transform([](int n){ return n * 2; })
    | to_vector();
};

或者

std::vector foo
{
    to_vector(
        numbers
        | std::views::filter([](int n){ return n % 2 == 0; })
        | std::views::transform([](int n){ return n * 2; }))
};

2
我知道这里写的是C++20,但是未来的答案是C++23的std::ranges::to。它是一个函数模板,有多种使用方式,包括管道或函数(std::ranges::to<std::vector>(intsView)intsView | std::ranges::to<std::vector>()),以及隐式或显式值类型(std::ranges::to<std::set>(intsView)std::ranges::to<std::set<int>>(intView))。它还支持分配器。我们在我们的代码库中有一个大致相似的实现,并且经常使用它。有很多情况下,你想要将一堆值收集到一个容器中,std::ranges::to为此提供了一个很好、简洁、表达力强的API。它甚至支持映射:
// Project out the "foo" field of this->m_database
auto map = std::ranges::to<absl::flat_hash_map>(
    this->m_database | std::views::transform([&](const auto& row) {
        return std::pair{row.first, row.second.foo}; 
    })
);

1
标记这个作为新的答案。谢谢! - undefined
1
你也可以在管道符号后使用它,即 | std::views::transform(...) | std::ranges::to<...>() - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接