Каким образом `part_sum` в range-v3 не противоречит ссылочной семантике, не являющейся владельцем?

Рассматривать Как написать конвейер диапазона, который использует временные контейнеры?. Вопрос в том, как построить представление, трансформирующее каждый элемент. T используя некоторую заданную функцию

std::vector<T> f(T t);

соблюдая ограничение (заимствуя из верхнего ответа там), что

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

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


Я не понимаю, как это согласуется с поддержкой библиотеки partial_sum.

Рассмотрим следующее прославленное целое число:

#include <vector>
#include <iostream>
#include <memory>
#include <range/v3/all.hpp>

using namespace ranges;

struct glorified_int {
explicit glorified_int(int i) : m_i{std::make_shared<int>(i)} {}
operator int() const { return *m_i; }
std::shared_ptr<int> m_i;
};

glorified_int operator+(const glorified_int &lhs, const glorified_int &rhs) {
glorified_int ret{(int)lhs + (int)rhs};
return ret;
}

Это в основном просто оборачивает int в классе, хранящем его в std::shared_ptr, позволяя инициализировать, извлекать и добавлять. W.r.t. не владеющая ссылочной семантикой, я не вижу принципиальной разницы между ней и контейнером, таким как std::vector,

Диапазон, кажется, не имеет проблемы с применением partial_sum к этому, хотя:

int main() {
std::vector<glorified_int> vi{ glorified_int{1}, glorified_int{2} };
for(const auto &ps: vi | view::partial_sum())
std::cout << ps << std::endl;

Распечатывает

$ ./a.out
1
3

Разве (прославленное целое число) 3 не является здесь временным? Это, конечно, не часть оригинальной последовательности. Кроме того, частичная сумма является преобразованием с сохранением состояния, очевидно, поэтому как диапазон может гарантировать, что

Представления дешевы в создании и копировании и не имеют ссылочной семантики.

Копировать представление так же дорого, как и объект накопления.

Обратите внимание, что также нет проблем связать это дальше (то есть это не действие):

    vi | view::partial_sum() | view::take(10);

В чем же разница?


Полный код

#include <vector>
#include <iostream>
#include <memory>
#include <range/v3/all.hpp>

using namespace ranges;

struct glorified_int {
explicit glorified_int(int i) : m_i{std::make_shared<int>(i)} {}
operator int() const { return *m_i; }
std::shared_ptr<int> m_i;
};

glorified_int operator+(const glorified_int &lhs, const glorified_int &rhs) {
glorified_int ret{(int)lhs + (int)rhs};
return ret;
}

int main() {
std::vector<glorified_int> vi{ glorified_int{1}, glorified_int{2} };
for(const auto &ps: vi | view::partial_sum())
std::cout << ps << std::endl;
vi | view::partial_sum() | view::take(10);
}

4

Решение

Что делает представление представлением, так это то, что оно не принимает и не требует владения, копирования или изменения каких-либо элементов входного диапазона. Но представление не должно иметь никакого государства вообще. Четное take() или же filter() иметь немного состояние (счетчик и предикат соответственно).

В этом конкретном случае partial_sum не должен владеть ни одним из элементов входного диапазона. Это работа входного диапазона. Также не нужно копировать или изменять их. Ему просто нужно отслеживать свое собственное состояние — текущую сумму ( optional<glorified_int>) и двоичная функция, выполняющая суммирование plus). Он владеет одним из своих собственных объектов, но этот объект полностью существует за пределами входного диапазона. Это все еще делает это представлением, только состоянием.

Ты пишешь:

Копировать представление так же дорого, как и объект накопления.

Это правда. Но это также верно для многих взглядов. transform() Копировать это так же дорого, как и функцию, которую мы используем для преобразования представления, может быть, у вас есть огромное, дорогостоящее, монотонное распределение памяти.

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

// cheap version
for(const auto &ps: vi | view::partial_sum()) { ... }

// expensive version
std::vector<glorified_int> partial_sums;
if (!vi.empty()) {
auto it = vi.begin();
partial_sums.emplace_back(*it++);
for (; it != vi.end(); ++it) {
partial_sums.emplace_back(*it + partial_sums.back());
}
}
for (const auto &ps : partial_sums) { ... }

Нам явно не нужен весь partial_sums вектор, чтобы делать то, что мы хотим (если нам это нужно, ну, никак не обойтись). Представление предлагает нам дешевый способ, ну, посмотреть частичные суммы.

4

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]