Как написать шаблон С++ 11, который может принимать итератор const
Отвечая на этот вопрос на CodeReview, я думал о том, как можно написать функцию шаблона, чтобы указать const
-ness содержащегося объекта.
Чтобы быть конкретным, рассмотрим эту шаблонную функцию
#include <iostream>
#include <numeric>
#include <vector>
template <class It>
typename std::iterator_traits<It>::value_type average(It begin, It end) {
typedef typename std::iterator_traits<It>::value_type real;
real sum = real();
unsigned count = 0;
for ( ; begin != end; ++begin, ++count)
sum += *begin;
return sum/count;
}
int main()
{
std::vector<double> v(1000);
std::iota(v.begin(), v.end(), 42);
double avg = average(v.cbegin(), v.cend());
std::cout << "avg = " << avg << '\n';
}
Он принимает итератор и вычисляет среднее значение на основе содержащихся чисел, но гарантированно не изменять вектор через прошедшие итераторы. Как передать это пользователю шаблона?
Обратите внимание, что объявление этого типа:
template <class It>
typename std::iterator_traits<It>::value_type average(const It begin,
const It end)
не работает, потому что это не итератор, а то, на что указывает итератор, что const
. Должен ли я ждать, пока стандарты будут стандартизированы?
Обратите внимание, что я не хочу требовать константных итераторов, но вместо этого укажу, что они могут быть безопасно использованы здесь. То есть, вместо того, чтобы ограничивать вызывающего абонента, я хочу передать обещание, которое делает мой код: "Я не буду изменять ваши базовые данные".
Ответы
Ответ 1
template <class ConstIt>
Это так просто. Здесь нет ничего, что можно было бы задействовать на стороне вызывающего абонента, так как итератор не const
также можно использовать для доступа const
, поэтому это просто документация по API, и что ваш выбор идентификатора параметра - документация API.
Это приводит к вопросу о принудительном исполнении на стороне вызываемого абонента/функции - поэтому он не может притворяться, что использует только итератор для доступа const
, а затем модифицирует элементы. Если вас это волнует, вы можете принять параметр, используя какой-то идентификатор, дающий понять, что он не предназначен для использования повсюду в функции, а затем создайте версию const_iterator
с более удобным идентификатором. Это может быть сложно, поскольку, как правило, вы не знаете, является ли тип итератора членом контейнера, не говоря уже о том, что это тип контейнера, и имеет ли он const_iterator
тоже, поэтому какой-то концепт действительно будет идеальным - пальцы скрещены для С++ 14. В то же время:
- сообщите своему абоненту тип контейнера,
- напишите свои собственные черты, ИЛИ
- напишите простой класс-оболочку, который содержит итератор и гарантирует, что только
const
доступ к указанным данным ускоряет интерфейс
Этот последний подход к оболочке показан ниже (не все API-интерфейс итератора реализован так, как требуется):
template <typename Iterator>
class const_iterator
{
public:
typedef Iterator iterator_type;
typedef typename std::iterator_traits<Iterator>::difference_type difference_type;
// note: trying to add const to ...:reference or ..:pointer doesn't work,
// as it like saying T* const rather than T const* aka const T*.
typedef const typename std::iterator_traits<Iterator>::value_type& reference;
typedef const typename std::iterator_traits<Iterator>::value_type* pointer;
const_iterator(const Iterator& i) : i_(i) { }
reference operator*() const { return *i_; }
pointer operator->() const { return i_; }
bool operator==(const const_iterator& rhs) const { return i_ == rhs.i_; }
bool operator!=(const const_iterator& rhs) const { return i_ != rhs.i_; }
const_iterator& operator++() { ++i_; return *this; }
const_iterator operator++(int) const { Iterator i = i_; ++i_; return i; }
private:
Iterator i_;
};
Использование образца:
template <typename Const_Iterator>
void f(const Const_Iterator& b__, const Const_Iterator& e__)
{
const_iterator<Const_Iterator> b{b__}, e{e__}; // make a really-const iterator
// *b = 2; // if uncommented, compile-time error....
for ( ; b != e; ++b)
std::cout << *b << '\n';
}
Посмотрите на ideone.com здесь.
Ответ 2
Вы можете добавить некоторые черты, чтобы увидеть, является ли итератор const_iterator:
template <typename IT>
using is_const_iterator =
std::is_const<typename std::remove_reference<typename std::iterator_traits<IT>::reference>::type>;
И затем используйте что-то вроде:
template <typename IT>
typename
std::enable_if<is_const_iterator<IT>::value,
typename std::iterator_traits<It>::value_type
>::type average(It begin, It end);
Но это позволит избежать использования итератора, которые конвертируются в const_iterator.
Поэтому лучше ограничивать итератор, когда const запрещен (как в std::sort
)
Ответ 3
Он принимает итератор и вычисляет среднее значение на основе содержащихся чисел, но гарантированно не изменять вектор через прошедшие итераторы. Как передать это пользователю шаблона?
Вы можете использовать SFINAE, чтобы отключить шаблон при передаче не-const-итераторов, но это было бы ненужным ограничением.
Другой способ - принять диапазоны вместо итераторов. Таким образом, вы можете написать:
template <class Range>
typename Range::value_type average(Range const& range);
Пользователь может передавать там контейнер или итератор.
Ответ 4
Вы можете попробовать разыменовать итератор с помощью некоторой функции deref()
.
template <typename It>
typename ::std::remove_reference<typename ::std::iterator_traits<It>::reference>::type const&
deref(It it)
{
return *it;
}
Что бы гарантировать, что базовое значение не будет изменено.