range-v3 的"partial_sum"如何不与非拥有引用语义相矛盾?



考虑如何编写一个使用临时容器的范围管道?问题是如何建立一个视图转换每个元素T使用一些给定的函数

std::vector<T> f(T t);

同时遵守

的限制(借鉴上面的答案)

视图是一种轻量级包装器,它以某种自定义的方式呈现底层元素序列的视图,而不需要改变或复制它。视图的创建和复制成本很低,并且具有非所属引用语义。

基本上,所有的答案似乎都同意,由于这个限制,它不能通过视图来完成。


我不明白这与支持partial_sum的库是如何匹配的。

考虑以下经过美化的整数:

#include <vector>
#include <iostream>
#include <memory>
#include <range/v3/all.hpp>
using namespace ranges;
struct glorified_int {
    explicit glorified_int(int i) : m_i{std::make_shared<int>(i)} {}
    operator int() const { return *m_i; }
    std::shared_ptr<int> m_i;
};
glorified_int operator+(const glorified_int &lhs, const glorified_int &rhs) {
    glorified_int ret{(int)lhs + (int)rhs};
    return ret;
}

它基本上只是将int封装在一个类中,将其存储在std::shared_ptr中,允许初始化,提取和添加。W.r.t.非拥有引用语义,我看不出它与std::vector等容器之间的根本区别。

Range在此应用partial_sum似乎没有问题,但是:

int main() {
    std::vector<glorified_int> vi{ glorified_int{1}, glorified_int{2} };
    for(const auto &ps: vi | view::partial_sum())
        std::cout << ps << std::endl;

输出

$ ./a.out
1 
3

这里(美化的整数of) 3不是临时的吗?这肯定不是原始序列的一部分。而且,部分和显然是一种有状态转换,那么range如何保证

视图的创建和复制成本很低,并且具有非所属引用语义。

复制视图和复制累积对象一样昂贵。

请注意,进一步链接也没有问题(即,它不是一个动作):

    vi | view::partial_sum() | view::take(10);

那么有什么区别呢?


完整代码

#include <vector>
#include <iostream>
#include <memory>
#include <range/v3/all.hpp>
using namespace ranges;
struct glorified_int {
    explicit glorified_int(int i) : m_i{std::make_shared<int>(i)} {}
    operator int() const { return *m_i; }
    std::shared_ptr<int> m_i;
};
glorified_int operator+(const glorified_int &lhs, const glorified_int &rhs) {
    glorified_int ret{(int)lhs + (int)rhs};
    return ret;
}
int main() {
    std::vector<glorified_int> vi{ glorified_int{1}, glorified_int{2} };
    for(const auto &ps: vi | view::partial_sum())
        std::cout << ps << std::endl;
    vi | view::partial_sum() | view::take(10);
}

使视图成为视图的原因是它不需要或不需要对输入范围的任何元素拥有所有权、复制或修改。但是视图并不需要没有任何状态。甚至take()filter()也有一些状态(分别是计数器和谓词)。

在本例中,partial_sum不必拥有输入范围的任何元素。这就是输入范围的作用。它也不需要复制或修改它们。它只需要跟踪自己的状态——运行和(optional<glorified_int>)和进行求和的二进制函数(plus)。它拥有一个自己的对象,但该对象完全存在于输入范围之外。这仍然使它成为一个视图,只是一个有状态的视图。

你写的

:

复制视图和复制累积对象一样昂贵。

这是真的。但这也适用于许多观点。复制transform()和我们用来转换视图的函数一样昂贵,也许你有一个巨大的有状态的、昂贵的、内存分配的怪物。

当Eric写到创建和复制成本低时,我相信他指的是创建和复制整个输入范围以产生一个新的范围。虽然partial_sum()需要保持运行和,这在您的情况下并不便宜,因为该元素需要分配,但这仍然比编写基于操作的partial_sum便宜得多:

// cheap version
for(const auto &ps: vi | view::partial_sum()) { ... }
// expensive version
std::vector<glorified_int> partial_sums;
if (!vi.empty()) {
    auto it = vi.begin();
    partial_sums.emplace_back(*it++);
    for (; it != vi.end(); ++it) {
        partial_sums.emplace_back(*it + partial_sums.back());
    }
}
for (const auto &ps : partial_sums) { ... }

我们显然不需要整个partial_sums向量来做我们想要的(如果我们确实需要它,好吧,没有办法)。这个观点为我们提供了一种简单的方法,来研究部分和。

最新更新