Как правильно реализовать итератор и const_iterator в C ++ 17? - PullRequest
6 голосов
/ 01 ноября 2019

При реализации пользовательского контейнера я пришел к тому, что мне нужно было реализовать итераторы. Конечно, я не хотел писать код дважды для константного и неконстантного итератора. Я нашел этот вопрос , подробно описывающий возможную реализацию, подобную этой:

template<class T>
class ContainerIterator {
    using pointer = T*;
    using reference = T&;
    ...
};

template<class T>
class Container {
    using iterator_type = ContainerIterator<T>;
    using const_iterator_type = ContainerIterator<const T>;
}

Но я также нашел этот вопрос , который использует параметр шаблона:

template<class T, bool IsConst>
class ContainerIterator {
    using pointer = std::conditional_t<IsConst, const T*, T*>;
    using reference = std::conditional_t<IsConst, const T&, T&>;
    ...
};

template<class T>
class Container {
    using iterator_type = ContainerIterator<T, false>;
    using const_iterator_type = ContainerIterator<T, true>;
}

Первое решение кажется более простым, но ответ - с 2010 года. После некоторых исследований кажется, что первая версия не получила широкого распространения, но я не понимаю, почему. Мне кажется, что я упускаю какой-то очевидный недостаток первой версии.


Таким образом, возникают вопросы:

  1. Есть ли какие-либопроблемы с первой версией?

  2. Если нет, почему версия # 2 кажется предпочтительным способом в c ++ 17? Или почему я должен предпочесть одно другому?


Кроме того, да, было бы решением использовать const_cast или просто продублировать весь код. Но мне не нравятся эти двое.

1 Ответ

2 голосов
/ 01 ноября 2019

Суть второй реализации - это то, что вы не скопировали: чтобы было проще реализовать определенное требование. А именно, iterator должен быть неявно конвертируемым в const_iterator.

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

template<class T>
class ContainerIterator {
    using pointer = T*;
    using reference = T&;
    ...
    ContainerIterator(const ContainerIterator &) = default; //Should be trivially copyable.
    ContainerIterator(const ContainerIterator<const T> &) {...}
};

Это не сработает. const const Typename разрешается до const Typename. Поэтому, если вы создаете экземпляр ContainerIterator с помощью const T, то теперь у вас есть два конструктора с одинаковой сигнатурой, один из которых по умолчанию. Что ж, это означает, что компилятор будет игнорировать ваши значения по умолчанию для конструктора копирования и, таким образом, использовать вашу нетривиальную реализацию конструктора копирования.

Это плохо.

Есть способы избежать этого, используя некоторыеИнструменты метапрограммирования для определения константности T, но самый простой способ исправить это - указать константу в качестве параметра шаблона:

template<class T, bool IsConst>
class ContainerIterator {
    using pointer = std::conditional_t<IsConst, const T*, T*>;
    using reference = std::conditional_t<IsConst, const T&, T&>;
    ...

    ContainerIterator(const ContainerIterator &) = default; //Should be trivially copyable.
    template<bool was_const = IsConst, class = std::enable_if_t<IsConst || !was_const>>>
    ContainerIterator(const ContainerIterator<T, was_const> &) {...}
};

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

Более подробную информацию об этом шаблоне можно найти также .

...