Могу ли я вернуть временный канал в операцию диапазона? - PullRequest
9 голосов
/ 19 марта 2020

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

Ответы [ 2 ]

4 голосов
/ 29 марта 2020

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

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

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

Из документации range-v3

Представление - это облегченная оболочка, которая представляет представление базовой последовательности элементов некоторым пользовательским способом, не изменяя и не копируя его. Представления дешевы в создании и копировании и имеют несобственную семантику ссылок.

и:

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

Разрушение нижележащего контейнера, очевидно, делает недействительными все итераторы к нему.

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

Если бы не было оператора трубы, это, вероятно, выглядело бы примерно так:

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

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

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

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

Если вы хотите, чтобы ваша функция возвращала диапазон как контейнер, вам нужно явно "материализовать" результат. Для этого используйте оператор ranges::to внутри функции.


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

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

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

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

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

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

[...]

views::transform

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

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

1 голос
/ 27 марта 2020

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

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

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

...