Является ли std :: span допустимым итератором :: ссылочным типом? - PullRequest
0 голосов
/ 16 апреля 2020

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

template<class T, int N>
class chunk_iterator {
  public:
    using reference = std::span<T,N>;

    chunk_iterator(T* ptr): ptr(ptr) {}

    chunk_iterator operator++() { ptr += N; return *this; }
    reference operator*() const { return {ptr,N}; }
  private:
    T* ptr;
};

Проблема, которую я вижу здесь, состоит в том, что std::span является представлением -подобная вещь, но она не ведет себя как ссылка (скажем, std::array<T,N>& в этом случае). В частности, если я присваиваю span, присваивание мелкое, оно не будет копировать значение.

Является ли std::span допустимым iterator::reference типом? Семантика view и reference подробно объяснена где-нибудь?

Что я должен сделать, чтобы решить мою проблему? Реализовать span_ref с правильной семантикой reference? Это уже реализовано в какой-то библиотеке? Можно ли вообще использовать неродной тип reference?

(примечание: решение проблемы путем сохранения std::array<T,N> и возврата std::array<T,N>& в operator* выполнимо, но безобразно, и если N неизвестно во время компиляции, вместо этого std::vector<T> с динамическим c выделение памяти просто неправильно)

Ответы [ 2 ]

1 голос
/ 16 апреля 2020

Значение типа reference итератора не может быть понято без понимания его связи с value_type итератора. Итератор - это конструкция, которая представляет позицию в последовательности value_type с. reference является посредником в этой парадигме; это вещь, которая действует как value_type (const) &. Пока вы не выясните, каким будет ваш value_type, вы не сможете решить, как должен выглядеть ваш reference.

Что означает «действует как», зависит от того, какой итератор мы » мы говорим о.

Для C ++ 11 категория InputIterator требует, чтобы reference был типом, который неявно преобразуется в value_type. Для категории OutputIterator reference должен быть типом, который можно назначить из a value_type.

Для всех более ограниченных категорий итераторов (ForwardIterator и выше), reference должно быть точно один из value_type & (если вы можете записать в последовательность) или value_type const & (если вы можете только читать из последовательности).

Итераторы где reference не value_type (const) & часто называют прокси-итераторами, поскольку тип reference обычно действует как «прокси» для фактических данных, хранящихся в последовательности (при условии, что итератор не просто придумывает значения для начала) , Прокси-итераторы часто используются в тех случаях, когда итератор не выполняет итерацию в диапазоне фактических значений value_types, а просто притворяется. Это могут быть побитовые итераторы vector<bool> или итератор, который перебирает последовательность целых чисел в некотором полуоткрытом диапазоне [0, N).

Но прокси-итератор reference должен действовать как язык ссылки на ту или иную степень. InputIterator reference должен быть неявно конвертируемым в value_type. span<T, N> не может быть явно преобразовано в array<T, N> или в любой другой тип контейнера, который подходит для value_type. OutputIterator reference s должен быть назначен от value_type. И хотя span<T, N> может быть назначено из array<T, N>, операция присваивания не имеет того же , означающего . Для назначения reference OutputIterator следует изменить значения, хранящиеся в пределах последовательности. И это не так.

В любом случае вам сначала нужно изобрести value_type, который делает то, что вам нужно. Затем вам нужно создать правильный тип reference, который будет действовать как ссылка. И наконец ... ну, вы не можете сделать свой итератор ForwardIterator или выше, потому что C ++ 11 не поддерживает прокси-итераторы наиболее полезных категорий итераторов. Новая формулировка итераторов в C ++ 20 позволяет использовать прокси-итераторы для всего, что не является contiguous_iterator.

1 голос
/ 16 апреля 2020

Говоря о стандартных итераторах, это зависит от нескольких вещей:

Для соответствия Итератор s, почти не имеет значения, что тип reference, потому что стандарт не требует никакой семантики использования для типа reference. Но это также означает, что никто, кроме вас, не знает, как использовать ваш итератор.

Для соответствия Итератор ввода s, тип reference должен соответствовать указанной семантике. Обратите внимание, что для LegacyInputIterator выражение *it должно быть ссылкой, которую можно использовать как ссылку со всей нормальной семантикой, в противном случае код, использующий ваш итератор, не будет работать должным образом. Это означает, что чтение из reference сродни чтению со встроенной ссылки. В частности, следующее должно делать «нормальные» вещи:

auto value = *itr; // this should read a value

В этой ситуации тип представления, такой как span, не будет работать, потому что span больше похож на указатель, чем на ссылку: в приведенный выше фрагмент value будет span, не смотря на то, на что ссылается span.

Для соответствия Итератор вывода s, тип reference не имеет требований. Фактически, стандартные LegacyOutputIterator s, такие как std::back_insert_iterator, имеют void как reference тип.

Для соответствия Forward Iterator s и выше, стандарт фактически требует reference быть встроенной ссылкой. Это для поддержки использования, как показано ниже:

auto& ref  = *itr;
auto ptr   = &ref;             // this must create a pointer pointing to the original object
auto ref2  = *ptr;             // this must create a second, equivalent reference
auto other = std::move( ref ); // this must do a "move", which may be the same as a copy
ref        = other;            // this must assign "other"'s value back into the referred-to object

Если вышеупомянутое не сработало правильно, многие из стандартных алгоритмов не могли бы быть написаны в общем.

Говоря с span в частности, он действует скорее как указатель, чем как ссылка логически. Это может быть переназначено, чтобы указать на что-то еще. Взяв его адрес, вы создаете указатель на span, а не указатель на охватываемый контейнер. Вызов std::move для диапазона копирует диапазон и не перемещает содержимое диапазона. Встроенная ссылка T& будет ссылаться только на одну вещь после ее создания.

Создание несоответствующего reference, который фактически работает со стандартными алгоритмами, потребует перегрузки семейства типов operator* , operator-> и operator&, operator= и std::move, а также указатели моделирования, ссылки на lvalue и ссылки на rvalue.

...