Posso retornar uma tubulação temporária para uma operação de alcance?

9

Suponha que eu tenha uma generate_my_rangeclasse que modela a range(em particular, é regular). Então, o seguinte código está correto:

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)valorizada pelo (primeiro) operador de tubulação ou tenho uma referência pendente quando saio do generate_my_rangeescopo?

Seria o mesmo com a chamada funcional ranges::views::transform(my_custom_rng_gen(some_param),my_transform_op)?

Seria correto se eu usasse uma referência lvalue? por exemplo:

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

Se intervalos são obtidos por valores para essas operações, o que devo fazer se passar um lvalue ref para um contêiner? Devo usar um ranges::views::all(my_container)padrão?

Bérenger
fonte
My_custom_rng_gen (some_param) já está limitado? Você quer dizer algo como godbolt.org/z/aTF8RN sem o take (5)?
Porsche9II 25/03
@ Porsche9II Sim, este é um intervalo limitado. Digamos que seja um contêiner
Bérenger 26/03

Respostas:

4

Na biblioteca de intervalos, existem dois tipos de operações:

  • visualizações preguiçosas e que exijam a existência do contêiner subjacente.
  • ações ansiosas e, como resultado, produzem novos contêineres (ou modificam os existentes)

As visualizações são leves. Você os passa por valor e exige que os contêineres subjacentes permaneçam válidos e inalterados.

Na documentação do range-v3

Uma visualização é um invólucro leve que apresenta uma visualização de uma sequência subjacente de elementos de alguma maneira personalizada, sem modificá-la ou copiá-la. As visualizações são baratas para criar e copiar e possuem semânticas de referência não proprietárias.

e:

Qualquer operação no intervalo subjacente que invalide seus iteradores ou sentinelas também invalidará qualquer exibição que se refira a qualquer parte desse intervalo.

A destruição do contêiner subjacente obviamente invalida todos os iteradores para ele.

Em seu código você está specifially usando visualizações - Você usa ranges::views::transform. O cachimbo é apenas um açúcar sintático para facilitar a escrita do jeito que está. Você deve olhar a última coisa no tubo para ver o que produz - no seu caso, é uma visão.

Se não houvesse operador de tubo, provavelmente seria algo parecido com isto:

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

se houvesse várias transformações conectadas dessa maneira, você poderá ver como seria feio.

Assim, se my_custom_rng_genproduz algum tipo de contêiner, que você transforma e depois retorna, esse contêiner é destruído e você tem referências pendentes da sua exibição. Se my_custom_rng_genhouver outra visão de um contêiner que mora fora desses escopos, está tudo bem.

No entanto, o compilador deve poder reconhecer que você está aplicando uma exibição em um contêiner temporário e receber um erro de compilação.

Se você deseja que sua função retorne um intervalo como um contêiner, é necessário "materializar" explicitamente o resultado. Para isso, use o ranges::tooperador dentro da função.


Atualização: para ser mais explícito em relação ao seu comentário "onde a documentação diz que a faixa / tubulação de composição obtém e armazena uma visualização?"

Pipe é apenas um açúcar sintático para conectar coisas em uma expressão fácil de ler. Dependendo de como é usado, ele pode ou não retornar uma exibição. Depende do argumento do lado direito. No seu caso, é:

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

Portanto, a expressão retorna o que quer que views::transformretorne.

Agora, lendo a documentação da transformação:

Abaixo está uma lista dos combinadores de faixa lenta, ou visualizações, que o Range-v3 fornece, e um resumo sobre como cada um deles deve ser usado.

[...]

views::transform

Dado um intervalo de origem e uma função unária, retorne um novo intervalo em que cada elemento de resultado é o resultado da aplicação da função unária a um elemento de origem.

Portanto, ele retorna um intervalo, mas, como é um operador lento, esse intervalo é uma visualização, com toda a sua semântica.

CygnusX1
fonte
Está bem. O que é um pouco misterioso para mim ainda é como funciona quando passo um contêiner para o tubo (ou seja, o objeto de intervalo criado pela composição). Ele precisa armazenar uma visão do contêiner de alguma forma. Está feito ranges::views::all(my_container)? E se uma visão for passada para o pipe? Ele reconhece que recebeu um contêiner ou uma exibição? Precisa? Quão?
Bérenger
"O compilador deve ser capaz de reconhecer que você está aplicando uma exibição em um contêiner temporário e receber um erro de compilação" Isso foi o que eu pensei também: se eu fizer algo estúpido, significa um contrato com o tipo (sendo deixado à esquerda) valor) não é cumprido. Coisas assim são feitas pelo range-v3. Mas, neste caso, não há absolutamente nenhum problema. Compila E é executado. Portanto, pode haver um comportamento indefinido, mas ele não aparece.
Bérenger
Para garantir que seu código funcione corretamente por acidente ou se está tudo bem, eu precisaria ver o conteúdo de my_custom_rng_gen. Como exatamente o tubo e transforminteragir sob o capô não é importante. A expressão inteira assume um intervalo como argumento (um contêiner ou uma exibição para algum contêiner) e retorna uma exibição diferente para esse contêiner. O valor de retorno nunca será o proprietário do contêiner, porque é uma visualização.
CygnusX1 30/03
1

Retirado da documentação do range-v3 :

As visualizações [...] possuem semântica de referência não proprietária.

e

Ter um objeto de intervalo único permite pipelines de operações. Em um pipeline, um intervalo é preguiçosamente adaptado ou ansiosamente mutado de alguma forma, com o resultado imediatamente disponível para posterior adaptação ou mutação. A adaptação preguiçosa é manipulada por visualizações e a mutação ansiosa é manipulada por ações.

// 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"};

No código acima, o rng simplesmente armazena uma referência aos dados subjacentes e às funções de filtro e transformação. Nenhum trabalho é feito até que rng seja iterado.

Como você disse que o intervalo temporário pode ser considerado um contêiner, sua função retorna uma referência pendente.

Em outras palavras, você precisa garantir que o intervalo subjacente sobreviva à visualização ou esteja com problemas.

Rumburak
fonte
Sim, as visualizações não são proprietárias, mas onde a documentação diz que a faixa / tubulação de composição obtém e armazena uma visualização? Seria possível (e acho que é bom) ter a seguinte política: armazenar por valor se o intervalo for fornecido por uma referência rvalue.
Bérenger
11
@ Bérenger Adicionei um pouco mais da documentação dos intervalos. Mas o ponto realmente é: uma visão não é proprietária . Não importa se você entrega um valor.
Rumburak 29/03