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

时间:2016-10-01 12:20:01

标签: c++ range-v3

考虑How do I write a range pipeline that uses temporary containers?。问题是如何构建一个视图,使用某个给定的函数

转换每个元素T
std::vector<T> f(T t);

同时遵守the restriction(从那里的最佳答案借用)

  

视图是一个轻量级的包装器,它以某种自定义方式呈现基础元素序列的视图,而不会发生变异或复制。视图创建和复制起来很便宜,并且具有非拥有的引用语义。

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

我不明白这是如何适应支持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

范围似乎没有问题将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

不是(美化的整数)3暂时在这里吗?它肯定不是原始序列的一部分。此外,部分和是显着的有状态转换,所以范围如何保证

  

创建和复制视图很便宜,并且具有非拥有的引用语义。

视图与累积对象一样昂贵。

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

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

1 个答案:

答案 0 :(得分:4)

视图的观点是它不会占用或要求输入范围的任何元素的所有权,复制或修改。但是,要求一个观点不需要任何状态。即使take()filter()也有某种状态(分别是计数器和谓词)。

在这种特定情况下,partial_sum不必拥有输入范围的任何元素。这是输入范围的工作。它也不需要复制或修改它们。它只需要跟踪自己的状态 - 运行总和(optional<glorified_int>)和进行求和的二元函数(plus)。它拥有自己的一个对象,但该对象完全存在于输入范围之外。这仍然是一个观点,只是一个有状态的观点。

你写道:

  

视图与累积对象一样昂贵。

这是事实。但是,许多观点也是如此。 transform()和我们用来转换视图的函数一样昂贵,也许你有一个庞大的有状态,昂贵,内存分配的怪物。

当Eric写关于创建和复制的廉价时,我相信他的意思是创建和复制整个输入范围以产生新的范围。虽然partial_sum()需要保留运行金额,但在您的情况下,由于该元素需要分配而且不便宜,这仍然比编写基于操作的partial_sum便宜得多:< / p>

// 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向量来做我们想要的事情(如果我们确实需要它,那么,没办法)。该视图为我们提供了一种廉价的方法来查看部分金额。