Получение индекса предмета в ранжировании на основе вектора

c++ c++11 ranged-loops

881 просмотра

2 ответа

2243 Репутация автора

В C ++ 11 введен цикл for на основе рангов, который реализован внутри с использованием (const) итераторов, так что это:

std::vector<std::string> vec;

for(std::string &str : vec)
{
//...
}

в основном эквивалентно более многословному (да, это можно упростить с помощью auto):

for(std::vector<std::string>::iterator it = vec.begin(); it != vec.end(); ++it)
{
//...
}

Однако обычно нужен также индекс предмета. Со вторым подходом это просто:

auto index = it - vec.begin();

На дальнем расстоянии forэто не так просто. Но мне было интересно, было ли это хорошо и портативное решение, которое вообще избегает итераторов:

for(auto &str : vec)
{
    auto index = &str - &vec[0];
}

( constверсия будет такой же, но нужно следить за тем, чтобы не смешивать constнеконтейнерные ссылки с константной ссылкой, что не всегда может быть очевидным.)

Очевидно, что это зависит от нескольких предположений:

  • этот итератор вектора является просто ссылкой на элемент (вероятно, в стандарте?)

  • контейнер гарантированно смежный ( std::vectorесть ...)

  • внутренняя реализация ранжированных на основе (также, вероятно, в стандарте)

Автор: Resurrection Источник Размещён: 11.10.2016 03:22

Ответы (2)


6 плюса

14940 Репутация автора

Да, это правильное решение. Базовые данные гарантированно будут смежными ( std::vectorпредполагается, что это динамический массив, более или менее).

n4140 §23.3.6.1 [vector.overview] / 1

Элементы vectorхранятся смежно, а это означает , что если vесть vector<T, Allocator>где Tнекоторый тип кроме bool, то он подчиняется личности &v[n] == &v[0] + nдля всех0 <= n < v.size()

Автор: krzaq Размещён: 11.10.2016 03:23

18 плюса

200041 Репутация автора

Решение

Да, но я бы использовал vec.data()вместо этого. Преимущество использования .data()заключается в том, что несмежные stdконтейнеры не имеют его, поэтому ваш код надежно прекращает компиляцию, когда перебираемый контейнер не работает таким образом (например, dequeили std::vector<bool>). (Есть и другие незначительные преимущества, такие как std::addressofпроблемы, и тот факт, что он хорошо определен для пустых контейнеров, но они не так важны, особенно здесь.)

В качестве альтернативы мы пишем index_tподобную итератору оболочку:

template<class T>
struct index_t {
  T t;
  T operator*()const{ return t; }
  void operator++() { ++t; }
  friend bool operator==( index_t const& lhs, index_t const& rhs ) {
    return lhs.t == rhs.t;
  }
  friend bool operator!=( index_t const& lhs, index_t const& rhs ) {
    return lhs.t != rhs.t;
  }
};
template<class T>
index_t<T> index(T t) { return {t}; }

index_t<int>может быть использован для создания счетных for(:)циклов.

index_t<iterator>может использоваться для создания for(:)циклов, возвращающих итераторы .

template<class It>
struct range_t {
  It b,e;
  It begin() const {return b;}
  It end() const {return e;}
};
template<class It>
range_t<It> range( It s, It f ) { return {s,f}; }

template<class T>
range_t<index_t<T>>
index_over( T s, T f ) {
  return {{{s}}, {{f}}};
}
template<class Container>
auto iterators_of( Container& c ) {
  using std::begin; using std::end;
  return index_over( begin(c), end(c) );
}

Теперь мы можем перебирать итераторы контейнера.

for (auto it : iterators_of(vec))

живой пример .


Упомянутые итерации над целыми числами:

for (int i : index_over( 0, 100 ) )

мы также можем напрямую получить индексы контейнера:

template<class Container>
range_t< index_t<std::size_t> >
indexes_of( Container& c ) {
  return index_over( std::size_t(0), c.size() );
}
template<class T, std::size_t N>
range_t< index_t<std::size_t> >
indexes_of( T(&)[N] ) {
  return index_over( std::size_t(0), N );
}

что позволяет нам:

for( auto i : indexes_of( vec ) )

где iменяется от 0до vec.size()-1. Я считаю, что иногда с этим легче работать, чем с итератором zip или чем-то подобным.


Улучшения опущены:

Сделай index_tнастоящий input_iterator. Используйте std::moveи / или по std::forwardмере необходимости при создании индексов и диапазонов. Поддержка Сентиналс на полигонах. Сделать range_tинтерфейс богаче ( size, опциональный произвольным доступом [], empty, front, back, range_t range_t::without_front(n) constи т.д.

Автор: Yakk - Adam Nevraumont Размещён: 11.10.2016 03:36
Вопросы из категории :
32x32