Подтвердить что ты не робот

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

В С++ 11 реализован цикл, основанный на диапазоне, который внутренне реализуется с использованием (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 is...)

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

4b9b3361

Ответ 1

Да, но вместо этого я использовал бы 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 по мере необходимости при создании индексов и диапазонов. Поддержка Sentinals на диапазонах. Сделайте range_t интерфейс более богатым (size, необязательный произвольный доступ [], empty, front, back, range_t range_t::without_front(n) const и т.д.

Ответ 2

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

n4140 §23.3.6.1 [vector.overview]/1

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