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

Найти позицию элемента в С++ 11 для цикла?

Предположим, у меня есть следующий код:

vector<int> list;
for(auto& elem:list) {
    int i = elem;
}

Могу ли я найти положение elem в векторе, не поддерживая отдельный итератор?

4b9b3361

Ответ 1

Да, вы можете, просто возьмите немного массажа;)

Трюк состоит в том, чтобы использовать композицию: вместо того, чтобы перебирать контейнер непосредственно, вы "застегиваете" его индексом по пути.

Специализированный код на молнии:

template <typename T>
struct iterator_extractor { typedef typename T::iterator type; };

template <typename T>
struct iterator_extractor<T const> { typedef typename T::const_iterator type; };


template <typename T>
class Indexer {
public:
    class iterator {
        typedef typename iterator_extractor<T>::type inner_iterator;

        typedef typename std::iterator_traits<inner_iterator>::reference inner_reference;
    public:
        typedef std::pair<size_t, inner_reference> reference;

        iterator(inner_iterator it): _pos(0), _it(it) {}

        reference operator*() const { return reference(_pos, *_it); }

        iterator& operator++() { ++_pos; ++_it; return *this; }
        iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; }

        bool operator==(iterator const& it) const { return _it == it._it; }
        bool operator!=(iterator const& it) const { return !(*this == it); }

    private:
        size_t _pos;
        inner_iterator _it;
    };

    Indexer(T& t): _container(t) {}

    iterator begin() const { return iterator(_container.begin()); }
    iterator end() const { return iterator(_container.end()); }

private:
    T& _container;
}; // class Indexer

template <typename T>
Indexer<T> index(T& t) { return Indexer<T>(t); }

И используя его:

#include <iostream>
#include <iterator>
#include <limits>
#include <vector>

// Zipper code here

int main() {
    std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};

    for (auto p: index(v)) {
        std::cout << p.first << ": " << p.second << "\n";
    }
}

Вы можете видеть его на ideone, хотя ему не хватает поддержки цикла for-range, поэтому он менее симпатичный.

EDIT:

Просто вспомнил, что я должен проверять Boost.Range чаще. К сожалению, диапазон zip не найден, но я нашел perl: boost::adaptors::indexed. Однако для доступа к итератору требуется доступ к индексу. Позор: x

В противном случае с counting_range и общим zip я уверен, что можно было бы сделать что-то интересное...

В идеальном мире я бы предположил:

int main() {
    std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};

    for (auto tuple: zip(iota(0), v)) {
        std::cout << tuple.at<0>() << ": " << tuple.at<1>() << "\n";
    }
}

С zip автоматически создавая представление в виде набора кортежей ссылок и iota(0) просто создавая "ложный" диапазон, который начинается с 0 и просто отсчитывается до бесконечности (или, ну, максимум его типа...).

Ответ 2

jrok прав: диапазоны для циклов не предназначены для этой цели.

Однако в вашем случае его можно вычислить с помощью арифметики указателя, так как vector сохраняет свои элементы смежно (*)

vector<int> list;
for(auto& elem:list) { 
    int i = elem;
    int pos = &elem-&list[0]; // pos contains the position in the vector 

    // also a &-operator overload proof alternative (thanks to ildjarn) :
    // int pos = addressof(elem)-addressof(list[0]); 

}

Но это, очевидно, плохая практика, так как она запутывает код и делает его более хрупким (он легко ломается, если кто-то меняет тип контейнера, перегружает оператор & или заменяет "auto &" на "auto". отлаживать это!)

ПРИМЕЧАНИЕ. Непрерывность гарантируется для вектора в С++ 03, а массив и строка в стандарте С++ 11.

Ответ 3

Нет, вы не можете (по крайней мере, не без усилий). Если вам нужна позиция элемента, вы не должны использовать диапазон. Помните, что это просто удобный инструмент для наиболее распространенного случая: выполните код для каждого элемента. В менее распространенных случаях, когда вам нужно положение элемента, вы должны использовать менее удобный обычный цикл for.

Ответ 4

Если у вас есть компилятор с поддержкой С++ 14, вы можете сделать это в функциональном стиле:

#include <iostream>
#include <string>
#include <vector>
#include <functional>

template<typename T>
void for_enum(T& container, std::function<void(int, typename T::value_type&)> op)
{
    int idx = 0;
    for(auto& value : container)
        op(idx++, value);
}

int main()
{
    std::vector<std::string> sv {"hi", "there"};
    for_enum(sv, [](auto i, auto v) {
        std::cout << i << " " << v << std::endl;
    });
}

Работает с clang 3.4 и gcc 4.9 (не с 4.8); для обоих нужно установить -std=c++1y. Причина, по которой вам нужен С++ 14, - это параметры auto в лямбда-функции.

Ответ 5

Основываясь на ответе @Matthieu, есть очень элегантное решение, использующее упомянутый выше boost :: adapters :: indexed:

std::vector<std::string> strings{10, "Hello"};
int main(){
    strings[5] = "World";
    for(auto const& el: strings| boost::adaptors::indexed(0))
      std::cout << el.index() << ": " << el.value() << std::endl;
}

Можешь попробовать

Это работает почти так же, как упомянутое "идеальное мировое решение", имеет красивый синтаксис и является лаконичным. Обратите внимание, что тип el в этом случае является чем-то вроде boost::foobar<const std::string&, int>, поэтому он обрабатывает ссылку там и копирование не производится. Это даже невероятно эффективно: https://godbolt.org/g/e4LMnJ (код эквивалентен хранению собственной переменной-счетчика, которая настолько хороша, насколько это возможно)

Для полноты альтернативы:

size_t i = 0;
for(auto const& el: strings) {
  std::cout << i << ": " << el << std::endl;
  ++i;
}

Или используя непрерывное свойство вектора:

for(auto const& el: strings) {
  size_t i = &el - &strings.front();
  std::cout << i << ": " << el << std::endl;
}

Первый генерирует тот же код, что и версия адаптера буста (оптимально), а последний на 1 инструкцию длиннее: https://godbolt.org/g/nEG8f9

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

for(auto const& el: strings) {
  bool isLast = &el == &strings.back();
  std::cout << isLast << ": " << el << std::endl;
}

Это работает для каждого стандартного контейнера, но необходимо использовать auto&/auto const& (как описано выше), но это рекомендуется в любом случае. В зависимости от входных данных это также может быть довольно быстро (особенно, когда компилятор знает размер вашего вектора)

Замените &foo на std::addressof(foo) чтобы быть в безопасности для универсального кода.

Ответ 6

Если вы настаиваете на использовании диапазона, основанного на индексе, и знаете индекс, довольно просто поддерживать индекс, как показано ниже. Я не думаю, что есть более чистое/более простое решение для диапазонов, основанных на циклах. Но почему бы не использовать стандарт для (;;)? Вероятно, это сделает ваше намерение и код самым ясным.

vector<int> list;
int idx = 0;
for(auto& elem:list) {
    int i = elem;
    //TODO whatever made you want the idx
    ++idx;
}

Ответ 7

Существует удивительно простой способ сделать это

vector<int> list;
for(auto& elem:list) {
    int i = (&elem-&*(list.begin()));
}

где i будет вашим обязательным индексом.

Это использует тот факт, что векторы С++ всегда смежны.

Ответ 8

Я прочитал из ваших комментариев, что одной из причин, по которой вы хотите знать индекс, является знать, является ли этот элемент первым/последним в последовательности. Если это так, вы можете сделать

for(auto& elem:list) {
//  loop code ...
    if(&elem == &*std::begin(list)){ ... special code for first element ... }
    if(&elem == &*std::prev(std::end(list))){ ... special code for last element ... }
//  if(&elem == &*std::rbegin(list)){... (C++14 only) special code for last element ...}
//  loop code ... 
}

EDIT: Например, это означает, что контейнер пропускает разделитель в последнем элементе. Работает для большинства контейнеров, которые я могу себе представить (включая массивы), (онлайн-демонстрация http://coliru.stacked-crooked.com/a/9bdce059abd87f91):

#include <iostream>
#include <vector>
#include <list>
#include <set>
using namespace std;

template<class Container>
void print(Container const& c){
  for(auto& x:c){
    std::cout << x; 
    if(&x != &*std::prev(std::end(c))) std::cout << ", "; // special code for last element
  }
  std::cout << std::endl;
}

int main() {
  std::vector<double> v{1.,2.,3.};
  print(v); // prints 1,2,3
  std::list<double> l{1.,2.,3.};
  print(l); // prints 1,2,3
  std::initializer_list<double> i{1.,2.,3.};
  print(i); // prints 1,2,3
  std::set<double> s{1.,2.,3.};
  print(s); // print 1,2,3
  double a[3] = {1.,2.,3.}; // works for C-arrays as well
  print(a); // print 1,2,3
}

Ответ 9

Вот решение на основе макросов, которое, вероятно, превосходит большинство других по простоте, времени компиляции и качеству генерации кода:

#include <iostream>

#define fori(i, ...) if(size_t i = -1) for(__VA_ARGS__) if(i++, true)

int main() {
    fori(i, auto const & x : {"hello", "world", "!"}) {
        std::cout << i << " " << x << std::endl;
    }
}

Результат:

$ g++ -o enumerate enumerate.cpp -std=c++11 && ./enumerate 
0 hello
1 world
2 !

Ответ 10

Тобиас Видлунд написал хороший перечисляемый заголовок только в стиле Python для MIT (хотя С++ 17):

GitHub

Сообщение в блоге

Действительно приятно использовать:

std::vector<int> my_vector {1,3,3,7};

for(auto [i, my_element] : en::enumerate(my_vector))
{
    // do stuff
}

Ответ 11

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

int main() {
    std::vector<char> values = {'a', 'b', 'c'};
    std::for_each(begin(values), end(values), [i = size_t{}] (auto x) mutable {
        std::cout << i << ' ' << x << '\n';
        ++i;
    });
}