Вопрос по c++, c++11, iterator – Найти положение элемента в цикле C ++ 11 на основе диапазона для цикла?

69

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

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

Могу ли я найти должностьelem в векторе без ведения отдельного итератора?

Это не то, для чего основан диапазон (хе, это каламбур?) jrok
Связанные с:https://stackoverflow.com/q/28769156/364696 ShadowRanger
По двум причинам. Во-первых, все, что я хочу сделать (в данном случае), это посмотреть, нахожусь ли я на последнем элементе или нет :), а во-вторых, компилятор должен поддерживать его, почему я не могу получить к нему доступ? & Quot; это & Quot; переменная с областью видимости поддерживается компилятором, почему бы и нет? Или предоставьте альтернативный (но все же удобный) синтаксис, который, как это делает javascript, устанавливает переменную, которая изменяется по мере прохождения цикла. для (авто и индекс: список) Fred Finkle
Это невозможно в контейнерах STL, если не используетсяstd::find или какая-то другая функция перебора. Вы не можете заключить итераторы из содержащихся элементов. Почему бы не поддерживать итератор? Eitan T
@FredFinkle вы на самом деле правы,there is an iterator, но при использовании диапазона на основеfor loop, это внутреннее имя компилятора и поэтому не может использоваться в вашем коде. Поэтому, если вы действительно хотите знать, находитесь ли вы на последнем элементе, вам следует использоватьfor(;;) петля. iFreilicht

Ваш Ответ

10   ответов
0

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

#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 !
59

Хитрость в том, чтобы использовать композицию: вместо того, чтобы перебирать контейнер напрямую, вы & quot; zip & quot; это с индексом по пути.

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

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, Однако для получения индекса требуется доступ к итератору. Позор: х

В противном случае с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) просто создаем & quot; ложь & quot; диапазон, который начинается с0 и просто считается до бесконечности (или, ну, максимум его типа ...).

@Xeo: я согласен :)
@ildjarn: Да, у Boost.Iterators есть строительные блоки (кажется), однако нет соответствующего диапазона, что раздражает.
Как насчетcounting_range (или жеboost::counting_iterator) + boost::zip_iterator?
Это дает массу полезной информации. Благодарю. Я поиграюсь с кодом. Как я уже упоминал выше, «индекс» Код - это то, что я хотел бы, чтобы язык предоставил. Fred Finkle
Обратите внимание, что вы могли бы изменить свойIndexer также правильно принимать и хранить аргументы rvalue, изменяя тип_container к типу значения, если исходный аргумент является rvalue иstd::move/std::forward аргумент в.
2

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

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
}
@MarcGlisse,int i код был просто примером. Я уберу это, чтобы избежать путаницы. Даже если вы используетеsize перед циклом вам понадобится счетчик.
Кажется, легче объявитьint i=c.size(); перед циклом и тестомif(--i==0).
Обратите внимание (до необоснованного понижения голосов), что автор вопроса задает этот вопрос в контексте обнаружения последнего элемента в цикле for-ranged для контейнера. Для этого я не вижу причин, почему сравнивать&elem а также&*std::prev(std::end(list)) не будет работать или быть практичным. Я согласен с другим ответом, что для этого больше подходит итератор для, но все же.
18

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

10

вы можете сделать это в функциональном стиле:

#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, Причина, по которой вам нужен c ++ 14, заключается вauto параметры в лямбда-функции.

std::function использует стирание типа, которое стоит дорого. Почему бы не использоватьtemplate<typename T, typename Callable> void for_enum(T& container, Callable op) значит, вам не нужно платить за удаление типа?
3

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

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

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

0

Python для MIT (хотя C ++ 17):

GitHub

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

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

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

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

существует очень элегантное решение с использованием упомянутыхповышение :: адаптеры :: индексируется:

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) быть в безопасности для общего кода.

Это действительно элегантно!
Теперь, если только это не зависит от повышения ....
Я добавил 2 варианта с помощью сравнения сгенерированного кода для полной полноты, а также обратился к необходимости ОП (в комментариях) для обнаружения последнего элемента.
4

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

vector<int> list;
int idx = 0;
for(auto& elem:list) {
    int i = elem;
    //TODO whatever made you want the idx
    ++idx;
}
(idx означает «поддержание отдельного итератора»)
26

Тем не менее, в вашем случае это можно вычислить с помощью арифметики указателя, так как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]); 

}

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

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

& Quot;it easily breaks if someone ... overload the & operator& Quot; Вот чтоstd::addressof для. : -]
Почему бы не использовать std :: distance для определения позиции?
Не знал, что смежность гарантирована. Не хотел бы использовать это здесь, но полезно знать. Fred Finkle
Да, это указано в стандарте. Смежность гарантирована дляvector в C ++ 03 иarray а такжеstring в C ++ 11.
Вы правы. Таким образом, версия, защищенная от & amp; перегрузки, будет выглядеть следующим образом: int pos = addressof (elem) - addressof (list [0]); .... Оболочка итератора Мэтью М. намного лучше :)

Похожие вопросы