4

考虑如何编写使用临时容器的范围管道?. 问题是如何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;
}

它基本上只是将 an 包装int在一个类中,将其存储在 an 中std::shared_ptr,允许初始化、提取和添加。Wrt 非拥有引用语义,我看不出它与容器之间的根本区别,例如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);
}
4

1 回答 1

4

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

在这种特定情况下,partial_sum不必拥有输入范围的任何元素。这就是输入范围的工作。它也不需要复制或修改它们。它只需要跟踪自己的状态 - 运行总和 (an optional<glorified_int>) 和执行求和 (a ) 的二进制函数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向量来做我们想做的事情(如果我们确实需要它,那么,没办法)。该视图为我们提供了一种廉价的方式来查看部分总和。

于 2016-10-01T18:17:10.753 回答