Могу ли я вернуть временный канал в операцию диапазона?

9

Предположим, у меня есть generate_my_rangeкласс, который моделирует range(в частности, есть 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)?

Было бы правильно, если бы я использовал ссылку lvalue? например:

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

Если диапазоны взяты значениями для этих операций, что мне делать, если я передаю ссылку lvalue в контейнер? Должен ли я использовать ranges::views::all(my_container)шаблон?

Беранже
источник
My_custom_rng_gen (some_param) уже ограничен? Вы имеете в виду что-то вроде godbolt.org/z/aTF8RN без дубля (5)?
Porsche9II
@ Porsche9II Да, это ограниченный диапазон. Допустим, это контейнер
Беренгер

Ответы:

4

В библиотеке диапазонов есть два вида операций:

  • представления, которые ленивы и требуют, чтобы основной контейнер существовал.
  • действия, которые стремятся, и производят новые контейнеры в результате (или изменяют существующие)

Виды легкие. Вы передаете их по значению и требуете, чтобы нижележащие контейнеры оставались действительными и неизменными.

Из документации диапазонов-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оператор внутри функции.


Обновление: чтобы быть более точным в отношении вашего комментария "где документация говорит, что составление диапазона / трубопровода берет и сохраняет представление?"

Труба - просто синтаксический сахар, чтобы соединить вещи в легко читаемом выражении. В зависимости от того, как он используется, он может возвращать или не возвращать представление. Это зависит от аргумента правой части. В вашем случае это:

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

Таким образом, выражение возвращает все, что views::transformвозвращает.

Теперь, читая документацию о преобразовании:

Ниже приведен список ленивых комбинаторов диапазонов или представлений, которые предоставляет Range-v3, и краткое описание того, как каждый из них предназначен для использования.

[...]

views::transform

Если задан диапазон источника и унарная функция, верните новый диапазон, где каждый элемент результата является результатом применения унарной функции к элементу источника.

Таким образом, он возвращает диапазон, но поскольку это ленивый оператор, этот диапазон, который он возвращает, является представлением со всей его семантикой.

CygnusX1
источник
Хорошо. Что еще немного загадочно для меня, так это то, как это работает, когда я передаю контейнер в канал (т. Е. Объект диапазона, созданный композицией). Нужно как-то хранить вид контейнера. Это сделано с ranges::views::all(my_container)? А что, если представление передается в трубу? Распознает ли он, что передан контейнер или представление? Это нужно? Как?
Беренгер
«Компилятор должен быть в состоянии распознать, что вы применяете представление к временному контейнеру, и ударил вас с ошибкой компиляции». Я тоже так думал: если я делаю что-то глупое, это означает контракт на тип (левый значение) не выполняется. Вещи, как это сделано Range-V3. Но в этом случае нет абсолютно никаких проблем. Он компилирует и запускает. Таким образом, может быть неопределенное поведение, но оно не проявляется.
Беренгер
Чтобы убедиться, что ваш код работает правильно случайно или все в порядке, мне нужно просмотреть содержимое my_custom_rng_gen. Как именно труба и как transformвзаимодействуют под капотом, не важно. Целое выражение принимает диапазон в качестве аргумента (контейнер или представление некоторого контейнера) и возвращает другое представление этому контейнеру. Возвращаемое значение никогда не будет владеть контейнером, потому что это представление.
CygnusX1
1

Взято из документации диапазонов-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 не повторен.

Поскольку вы сказали, что временный диапазон можно рассматривать как контейнер, ваша функция возвращает висячую ссылку.

Другими словами, вам нужно убедиться, что базовый диапазон переживает представление, или у вас проблемы.

Rumburak
источник
Да, представления не принадлежат, но где в документации сказано, что составление диапазона / трубопровода берет и сохраняет представление? Было бы возможно (и я думаю, что это хорошо) иметь следующую политику: хранить по значению, если диапазон задается ссылкой rvalue.
Беренгер
1
@ Bérenger Я добавил немного больше из документации по диапазонам. Но дело в том, что точка зрения не принадлежит . Неважно, передадите ли вы это значение.
Румбурак