range-v3 的“partial_sum”如何不与非拥有引用语义相矛盾?
How does range-v3's `partial_sum` not contradict non-owning reference semantics?
考虑 How do I write a range pipeline that uses temporary containers?。问题是如何使用一些给定的函数
构建一个转换每个元素的视图 T
std::vector<T> f(T t);
同时遵守 the restriction(从那里的最佳答案借用)
A view is a lightweight wrapper that presents a view of an underlying sequence of elements in some custom way without mutating or copying it. Views are cheap to create and copy, and have non-owning reference semantics.
基本上,那里的所有答案似乎都同意,由于此限制,无法通过视图完成。
我不明白这与支持 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
包装在 class 中,将其存储在 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
3(的美化整数)在这里不是临时的吗?它肯定不是原始序列的一部分。此外,部分和显然是一种状态转换,那么范围如何保证
Views are cheap to create and copy, and have non-owning reference semantics.
复制视图与积累对象一样昂贵。
请注意,进一步链接也没有问题(即,这不是一个动作):
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
)。它拥有自己的对象之一,但该对象完全存在于输入范围之外。这仍然使它成为一个视图,只是一个有状态的视图。
你写:
The view is as expensive to copy as the accumulation object.
这是事实。但许多观点也是如此。 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
向量来做我们想做的事情(如果我们确实需要它,那也没有办法)。该视图为我们提供了一种廉价的方式来查看部分总和。
考虑 How do I write a range pipeline that uses temporary containers?。问题是如何使用一些给定的函数
构建一个转换每个元素的视图T
std::vector<T> f(T t);
同时遵守 the restriction(从那里的最佳答案借用)
A view is a lightweight wrapper that presents a view of an underlying sequence of elements in some custom way without mutating or copying it. Views are cheap to create and copy, and have non-owning reference semantics.
基本上,那里的所有答案似乎都同意,由于此限制,无法通过视图完成。
我不明白这与支持 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
包装在 class 中,将其存储在 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
3(的美化整数)在这里不是临时的吗?它肯定不是原始序列的一部分。此外,部分和显然是一种状态转换,那么范围如何保证
Views are cheap to create and copy, and have non-owning reference semantics.
复制视图与积累对象一样昂贵。
请注意,进一步链接也没有问题(即,这不是一个动作):
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
)。它拥有自己的对象之一,但该对象完全存在于输入范围之外。这仍然使它成为一个视图,只是一个有状态的视图。
你写:
The view is as expensive to copy as the accumulation object.
这是事实。但许多观点也是如此。 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
向量来做我们想做的事情(如果我们确实需要它,那也没有办法)。该视图为我们提供了一种廉价的方式来查看部分总和。