Какова роль тега std:: forward iterator?

при профилировании приложения я столкнулся с этой частью стандартной реализации библиотеки, поставляемой с gcc 4.7.1. Это include/g++-v4/bits/vector.tcc:

template<typename _Tp, typename _Alloc>
template<typename _ForwardIterator>
  void
  vector<_Tp, _Alloc>::
    _M_range_insert(iterator __position, _ForwardIterator __first,
          _ForwardIterator __last, std::forward_iterator_tag)
  {
     …
  }

Я заметил, что последний аргумент функции подписи-это просто тег, и я начал задаваться вопросом, почему он был здесь. Быстрый взгляд на на этой странице видно, что std::forward_iterator_tag - это пустая структура. Какова его роль здесь? Очевидно, что это бесполезно для функции, и это может потратить регистр или некоторое пространство в стеке. Так почему?

5 ответов


в двух словах: теги для перегрузки, для оптимизации.

возьмем простой advance в качестве примера вы можете создать:

template<class II, class D>
void advance(II& i, D n){
    while( n-- ) ++i;
}

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

template<class II, class D>
void advance_II(II& i, D n){
    while( n-- ) ++i;
}

template<class RAI, class D>
void advance_RAI(RAI& i, D n){
    i += n;
}

template<class II, class D>
void advance(II& i, D n){
    if(is_random_access_iterator(i)) // not yet designed
        advance_RAI(i, n);
    else
        advance_II(i, n);
}
на версия функции для использования решается во время выполнения, поэтому мы пытаемся позволить компилятору решить, какой метод выбрать во время компиляции. Поэтому мы даем итераторы теги. Есть пять тегов:
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirection_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirection_iterator_tag {};

теперь вы можете сделать это:

template<class II, class D>
void __advance(II& i, D n, input_iterator_tag){
    while( n-- ) ++i;
}

template<class RAI, class D>
void __advance(RAI& i, D n, random_access_iterator_tag){
    i += n;
}

template<class II, class D>
void advance(II& i, D n){
    __advance(i, n, iterator_traits<II>::iterator_category());
}

чтобы различать разные _M_range_insert перегрузок.

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


С риском цитирования вашей ссылки..

пустой класс для определения категории итератора как прямого итератора:

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

в моем impl, у меня есть

void _Insert(const_iterator _Where, _Iter _First, _Iter _Last,
            _Int_iterator_tag)

void _Insert(const_iterator _Where, _Iter _First, _Iter _Last,
            input_iterator_tag)

void _Insert(const_iterator _Where, _Iter _First, _Iter _Last, 
            forward_iterator_tag)

среди прочих перегрузок.


Это часть механизма метапрограммирования шаблонов, и он используется для выбора соответствующей перегрузки на основе признаков аргументов, поэтому, например, если у вас есть итераторы произвольного доступа, вы можете воспользоваться этим и проверить расстояние между ними и зарезервировать перед вставкой. С другой стороны, если у вас есть только прямые итераторы, проверка расстояния будет O(n), поэтому вы этого не делаете, просто нажмите назад, что может привести к нескольким перемещениям и, следовательно, будет медленнее. Также компилятор оптимизирует out эти пустые структуры, поэтому нет штрафа во время выполнения.


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

iterator insert( const_iterator pos, size_type count, const T& value );

template< class InputIt >
iterator insert( const_iterator pos, InputIt first, InputIt last );

если у вас vector<int> и звонок vec.insert(vec.bgein(), 5, 4), вы, конечно, хотите вставить 5 раз значение 4. Но разрешение перегрузки увидит шаблон и вызовет его, выводя InputIt на int.

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

  • _Int_iterator_tag если InputIt имеет интегральный тип
  • forward_iterator_tag если InputIt является прямым итератором (который включает итераторы произвольного доступа)
  • input_iterator_tag если InputIt - это тип итератора, который не является прямым итератором

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

  • перегрузка для Int-тега будет перенаправлена на первую не шаблонную перегрузку insert (или ее реализации)
  • перегрузка для прямых итераторов вызывает reserve(std::distance(first,last)) и копирует элементы из диапазона итератора после этого
  • перегрузка для простых входных итераторов просто копирует элементы, возможно, вызывающие множественные перераспределения. Он не может вызвать reserve, потому что входные итераторы могут быть оценены только один раз (например, итераторы istream)

метод templated insert будет выглядеть концептуально следующим образом:

template< class InputIt >
iterator insert( const_iterator pos, InputIt first, InputIt last )
{
  return _M_range_insert(pos, first, last, InsertIteratorTraits<InputIt>::tag_type());
}