我可以将临时管道返回到范围操作吗?


9

假设我有一个generate_my_range模拟a 的类range(尤其是is regular)。然后,以下代码正确:

auto generate_my_range(int some_param) {    
  auto my_transform_op = [](const auto& x){ return do_sth(x); };
  return my_custom_rng_gen(some_param) | ranges::views::transform(my_transform_op);
}
auto cells = generate_my_range(10) | ranges::to<std::vector>;

my_custom_rng_gen(some_param)由(第一个)管道运算符按值获取的,还是离开generate_my_range范围后是否有悬挂的引用?

函数调用会一样ranges::views::transform(my_custom_rng_gen(some_param),my_transform_op)吗?

如果我使用左值引用会正确吗?例如:

auto generate_my_range(int some_param) {
  auto my_transform_op = [](const auto& x){ return do_sth(x); };
  auto tmp_ref = my_custom_rng_gen(some_param);
  return tmp_ref | ranges::views::transform(my_transform_op);
}

如果这些操作的值取值范围,那么如果将左值ref传递给容器该怎么办?我应该使用ranges::views::all(my_container)模式吗?


my_custom_rng_gen(some_param)已经绑定了吗?您的意思是诸如godbolt.org/z/aTF8RN之类的没有take(5)的东西吗?
Porsche9II

@ Porsche9II是的,这是一个有界范围。假设它是一个容器
贝伦格

Answers:


4

在范围库中,有两种操作:

  • 懒惰的视图,需要底层容器存在。
  • 动作,其渴望,并产生新的容器中,作为一个结果(或修改现有的)

视图是轻量级的。您按值传递它们,并要求基础容器保持有效和不变。

ranges-v3文档

视图是一种轻量级的包装器,它以某种自定义方式呈现元素的基础序列的视图,而无需对其进行更改或复制。视图的创建和复制便宜,并且具有非所有的引用语义。

和:

在基础范围上对其迭代器或前哨使其无效的任何操作也将使引用该范围任何部分的任何视图无效。

基础容器的破坏显然会使所有迭代器无效。

在您的代码中,您正在特定地使用视图 -您使用ranges::views::transform。管道只是一种语法糖,使编写方式变得容易。您应该查看管道中的最后一件事,以查看所产生的内容-在您的情况下,它是一个视图。

如果没有管道运算符,则可能看起来像这样:

ranges::views::transform(my_custom_rng_gen(some_param), my_transform_op)

如果以这种方式连接了多个转换,则可以看到它会变得多么丑陋。

因此,如果my_custom_rng_gen生成某种容器,然后进行转换然后返回,则该容器将被销毁,并且您的视图中有悬挂的引用。如果my_custom_rng_gen是对不在这些范围内的容器的另一种看法,那么一切都很好。

但是,编译器应该能够识别出您正在对临时容器应用视图,并且遇到编译错误。

如果希望函数将范围作为容器返回,则需要显式“实现”结果。为此,请ranges::to在函数内使用运算符。


更新:为了使您的评论更加真实,“文档在哪里说组成范围/管道需要并存储视图?”

Pipe只是一种语法糖,用于以易于阅读的表达式连接事物。根据使用方式的不同,它可能会也可能不会返回视图。它取决于右侧的参数。您的情况是:

`<some range> | ranges::views::transform(...)`

因此,表达式返回任何views::transform返回值。

现在,通过阅读转换文档:

下面是Range-v3提供的惰性范围组合器或视图的列表,以及有关每种用途的使用说明。

[...]

views::transform

给定一个源范围和一元函数,返回一个新范围,其中每个结果元素是将一元函数应用于源元素的结果。

因此它返回一个范围,但是由于它是一个惰性运算符,因此它返回的范围一个具有所有语义的视图。


好。对我来说,仍然有些神秘的是,当我将容器传递到管道(即由合成创建的范围对象)时,它是如何工作的。它需要以某种方式存储容器的视图。完成了ranges::views::all(my_container)吗?如果将视图传递给管道怎么办?它是否识别它已通过容器或视图?需要吗?怎么样?
贝伦格

“编译器应该能够识别出您正在对一个临时容器应用视图,并遇到编译错误。”那也是我的想法:如果我做一些愚蠢的事情,那就意味着该类型的契约(左)值)未实现。诸如此类的东西是由range-v3完成的。但是在这种情况下,绝对没有问题。它编译并运行。因此,可能存在未定义的行为,但未显示。
贝伦格

为确保您的代码是偶然运行正确的,或者一切正常,我需要查看中的内容my_custom_rng_gen。管道与transform引擎盖之间如何精确交互并不重要。整个表达式将范围作为参数(容器或某个容器的视图),并向该容器返回不同的视图。返回值永远不会拥有该容器,因为它是一个视图。
CygnusX1

1

取自ranges-v3文档

视图具有非所有的引用语义。

具有单个范围的对象允许进行操作的流水线。在管道中,范围以某种方式延迟适应或急切变异,结果立即可用于进一步适应或变异。懒惰的适应由视图处理,渴望的突变由操作处理。

// taken directly from the the ranges documentation
std::vector<int> const vi{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
using namespace ranges;
auto rng = vi | views::remove_if([](int i){ return i % 2 == 1; })
              | views::transform([](int i){ return std::to_string(i); });
// rng == {"2","4","6","8","10"};

在上面的代码中,rng仅存储对基础数据以及过滤器和转换函数的引用。在迭代rng之前,不做任何工作。

因为您说过可以将临时范围视为一个容器,所以您的函数将返回一个悬空的引用。

换句话说,您需要确保基础范围超过视图的寿命,否则您将遇到麻烦。


是的,视图没有所有权,但是文档在哪里说组成范围/管道需要并存储视图?可能有以下政策(我认为这是一件好事):如果范围是由右值引用给定的,则按值存储。
贝伦格

1
@Bérenger我从范围文档中添加了更多内容。但重点确实是:视图是无主的。不管您是否将其交给右值。
拉姆布拉克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.