C++ 如何编写使用临时容器的范围管道?
我有一个具有此签名的第三方功能:C++ 如何编写使用临时容器的范围管道?,c++,range-v3,C++,Range V3,我有一个具有此签名的第三方功能: std::vector<T> f(T t); 但是,这不起作用,因为我们无法创建临时容器的视图 range-v3是如何支持这样的范围管道的?我怀疑它就是不能。没有一个视图有任何机制可以在任何地方存储临时数据-这明显违背了以下视图的概念: 视图是一种轻量级包装器,它以某种自定义方式呈现底层元素序列的视图,而无需对其进行修改或复制。视图的创建和复制成本很低,并且具有非拥有的引用语义 因此,为了使join起作用并超过表达式的寿命,必须在某个地方保留这些临
std::vector<T> f(T t);
但是,这不起作用,因为我们无法创建临时容器的视图
range-v3是如何支持这样的范围管道的?我怀疑它就是不能。没有一个
视图
有任何机制可以在任何地方存储临时数据-这明显违背了以下视图的概念:
视图是一种轻量级包装器,它以某种自定义方式呈现底层元素序列的视图,而无需对其进行修改或复制。视图的创建和复制成本很低,并且具有非拥有的引用语义
因此,为了使join
起作用并超过表达式的寿命,必须在某个地方保留这些临时变量。这可能是一个动作
。这将起作用():
除了显然不是因为src
是无限的,而且即使是有限的src
也可能会增加太多的开销,使您无论如何都不想使用
您可能需要复制/重写
view::join
,以使用view::all
(必需)的一些经过巧妙修改的版本,该版本不需要左值容器(并在其中返回迭代器对),而是允许在内部存储右值容器(并将迭代器对返回到存储的版本中)。但这需要复制几百行代码,因此即使这样做有效,似乎也不太令人满意。编辑
显然,下面的代码违反了视图不能拥有其引用的数据的规则(但是,我不知道是否严格禁止编写这样的代码)
我使用ranges::view\u facade
创建一个自定义视图。它保存一个由f
返回的向量(一次一个),将其更改为一个范围。这使得可以在这样的范围内使用view::join
。当然,我们不能对元素进行随机或双向访问(但是,view::join
本身会将一个范围降级为一个输入范围),我们也不能分配给它们
我从Eric Niebler那里复制了struct MyRange
,并对其进行了轻微修改
#include <iostream>
#include <range/v3/all.hpp>
using namespace ranges;
std::vector<int> f(int i) {
return std::vector<int>(static_cast<size_t>(i), i);
}
template<typename T>
struct MyRange: ranges::view_facade<MyRange<T>> {
private:
friend struct ranges::range_access;
std::vector<T> data;
struct cursor {
private:
typename std::vector<T>::const_iterator iter;
public:
cursor() = default;
cursor(typename std::vector<T>::const_iterator it) : iter(it) {}
T const & get() const { return *iter; }
bool equal(cursor const &that) const { return iter == that.iter; }
void next() { ++iter; }
// Don't need those for an InputRange:
// void prev() { --iter; }
// std::ptrdiff_t distance_to(cursor const &that) const { return that.iter - iter; }
// void advance(std::ptrdiff_t n) { iter += n; }
};
cursor begin_cursor() const { return {data.begin()}; }
cursor end_cursor() const { return {data.end()}; }
public:
MyRange() = default;
explicit MyRange(const std::vector<T>& v) : data(v) {}
explicit MyRange(std::vector<T>&& v) noexcept : data (std::move(v)) {}
};
template <typename T>
MyRange<T> to_MyRange(std::vector<T> && v) {
return MyRange<T>(std::forward<std::vector<T>>(v));
}
int main() {
auto src = view::ints(1); // infinite list
auto rng = src | view::transform(f) | view::transform(to_MyRange<int>) | view::join;
for_each(rng | view::take(42), [](int i) {
std::cout << i << ' ';
});
}
// Output:
// 1 2 2 3 3 3 4 4 4 4 5 5 5 5 5 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 9 9 9 9 9 9
#包括
#包括
使用名称空间范围;
标准::向量f(int i){
返回std::vector(静态_cast(i),i);
}
模板
struct MyRange:ranges::view\u facade{
私人:
friend struct ranges::range\u access;
std::矢量数据;
结构游标{
私人:
typename std::vector::const_迭代器iter;
公众:
游标()=默认值;
游标(typename std::vector::const_iterator it):iter(it){}
T const&get()const{return*iter;}
bool equal(cursor const&that)const{return iter==that.iter;}
void next(){++iter;}
//输入范围不需要这些:
//void prev(){--iter;}
//std::ptrdiff_t distance_to(cursor const&that)const{return that.iter-iter;}
//无效前进(std::ptrdiff_t n){iter+=n;}
};
游标开始\游标()常量{return{data.begin()};}
游标结束\u游标()常量{return{data.end()};}
公众:
MyRange()=默认值;
显式MyRange(const std::vector&v):数据(v){}
显式MyRange(std::vector&&v)noexcept:data(std::move(v)){}
};
模板
MyRange到_MyRange(std::vector&&v){
返回MyRange(std::forward(v));
}
int main(){
auto src=view::ints(1);//无限列表
auto rng=src | view::transform(f)| view::transform(to_MyRange)| view::join;
每个(rng |视图::取(42),[](int i){
std::cout这里的问题当然是一个视图的整体概念——一个非存储的分层延迟求值器。为了跟上这个约定,视图必须传递对范围元素的引用,通常它们可以处理右值和左值引用
不幸的是,在这种特定情况下,view::transform
只能提供一个右值引用,因为函数f(T)
按值返回一个容器,view::join
在尝试将视图(view::all
)绑定到内部容器时需要一个左值
可能的解决方案都会在管道的某处引入某种临时存储。以下是我提出的选项:
- 创建一个版本的
view::all
,该版本可以在内部存储通过右值引用传递的容器(如Barry所建议的)
“非存储视图”的概念,还需要一些痛苦的模板
所以我建议不要使用这个选项
- 在
view::transform
步骤之后,为整个中间状态使用临时容器。可以手动完成:
auto rng1 = src | view::transform(f)
vector<vector<T>> temp = rng1;
auto rng = temp | view::join;
然后将f_-store
传递到view::transform
。由于f_-store
返回左值引用,view::join
现在不会抱怨
当然,这有点像黑客,只有将整个范围简化到某个接收器(如输出容器)中才能起作用。我相信它可以经受一些简单的转换,如view::replace
或更多view::transform
s,但任何更复杂的东西都可以尝试访问这个temp
stora通用电气以非直截了当的顺序
在这种情况下,可以使用其他类型的存储,例如,std::map
将修复该问题,并且仍然允许无限src
和延迟计算,但会牺牲一些内存:
const std::vector<T>& fc(const T& t)
{
static std::map<T, vector<T>> smap;
smap[t] = f(t);
return smap[t];
}
const std::vector&fc(const T&T)
{
静态std::map smap;
smap[t]=f(t);
返回smap[t];
}
如果你的f
函数是无状态的,这个std::map
也可以用来潜在地保存一些调用。如果有一种方法可以保证不再需要某个元素并将其从std::map
中删除以节省内存,那么这种方法可能会得到进一步改进。然而,这取决于
auto rng1 = src | view::transform(f)
vector<vector<T>> temp = rng1;
auto rng = temp | view::join;
const std::vector<T>& f_store(const T& t)
{
static std::vector<T> temp;
temp = f(t);
return temp;
}
const std::vector<T>& fc(const T& t)
{
static std::map<T, vector<T>> smap;
smap[t] = f(t);
return smap[t];
}
auto rng = src | view::transform(f) | view::join;
#include <iostream>
#include <vector>
#include <range/v3/range_for.hpp>
#include <range/v3/utility/functional.hpp>
#include <range/v3/view/iota.hpp>
#include <range/v3/view/join.hpp>
#include <range/v3/view/transform.hpp>
using T = int;
std::vector<T> f(T t) { return std::vector<T>(2, t); }
int main() {
std::vector<T> buffer;
auto store = [&buffer](std::vector<T> data) -> std::vector<T>& {
return buffer = std::move(data);
};
auto rng = ranges::view::ints
| ranges::view::transform(ranges::compose(store, f))
| ranges::view::join;
unsigned count = 0;
RANGES_FOR(auto&& i, rng) {
if (count) std::cout << ' ';
else std::cout << '\n';
count = (count + 1) % 8;
std::cout << i << ',';
}
}
#include <range/v3/core.hpp>
#include <range/v3/view/iota.hpp>
#include <range/v3/view/transform.hpp>
#include <range/v3/view/join.hpp>
#include <vector>
#include <iostream>
#include <memory>
std::vector<int> f(int i) {
return std::vector<int>(3u, i);
}
template <class Container>
struct shared_view : ranges::view_interface<shared_view<Container>> {
private:
std::shared_ptr<Container const> ptr_;
public:
shared_view() = default;
explicit shared_view(Container &&c)
: ptr_(std::make_shared<Container const>(std::move(c)))
{}
ranges::range_iterator_t<Container const> begin() const {
return ranges::begin(*ptr_);
}
ranges::range_iterator_t<Container const> end() const {
return ranges::end(*ptr_);
}
};
struct make_shared_view_fn {
template <class Container,
CONCEPT_REQUIRES_(ranges::BoundedRange<Container>())>
shared_view<std::decay_t<Container>> operator()(Container &&c) const {
return shared_view<std::decay_t<Container>>{std::forward<Container>(c)};
}
};
constexpr make_shared_view_fn make_shared_view{};
int main() {
using namespace ranges;
auto rng = view::ints | view::transform(compose(make_shared_view, f)) | view::join;
RANGES_FOR( int i, rng ) {
std::cout << i << '\n';
}
}
auto rng = views::iota(0,4)
| views::transform([](int i) {return std::string(i, char('a'+i));})
| views::cache1
| views::join('-');
check_equal(rng, {'-','b','-','c','c','-','d','d','d'});
CPP_assert(input_range<decltype(rng)>);
CPP_assert(!range<const decltype(rng)>);
CPP_assert(!forward_range<decltype(rng)>);
CPP_assert(!common_range<decltype(rng)>);
auto rng = src | views::transform(f) | views::cache1 | views::join;