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

Индекс доступа в цикле диапазона

У меня есть вектор объектов, и я повторяю его с помощью цикла range-for. Я использую его для печати функции из объекта, например:

vector<thisObject> storedValues;
//put stuff in storedValues
for(auto i:storedValues)
{
   cout<<i.function();
}

Но я тоже хочу напечатать индекс. Мой желаемый результат:

1: value
2: value
//etc

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

4b9b3361

Ответ 1

Вы не можете. Индекс является специфическим понятием для вектора, а не общим свойством коллекции. С другой стороны, петля на основе диапазона - это общий механизм для итерации по каждому элементу любой коллекции.

Если вы хотите использовать детали своей конкретной реализации контейнера, просто используйте обычный цикл:

for (std::size_t i = 0, e = v.size(); i != e; ++i) { /* ... */ }

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

Ответ 2

Я уверен, что некоторым это не понравится, но я создал макрос препроцессора, который обрабатывает это для вас (IMO) относительно чисто:

#define for_indexed(...) for_indexed_v(i, __VA_ARGS__)
#define for_indexed_v(v, ...) for(bool _i_ = true, _break_ = false; _i_;) for(size_t v = 0; _i_; _i_ = false) for(__VA_ARGS__) if(_break_) break; else for(bool _j_ = true; _j_;) for(_break_ = true; _j_; _j_ = false) for(bool _k_ = true; _k_; v++, _k_ = false, _break_ = false)

Пример использования:

std::vector<int> v {1, 2, 3};
for_indexed (const auto& item : v) {
    if (i > 0) std::cout << ", ";
    std::cout << i << ": " << item;
}

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

for_indexed_v (my_cool_counter, const auto& item : v) ...

Я дважды проверил, и все эти лишние -O1 оптимизированы для -O1 или выше. Вы остались с хорошим, легко читаемым синтаксисом цикла.

Бонус: это работает и для классических циклов iterator -style. (Хотя вы также можете использовать std::distance(begin, it).)

Обновление 28.05.2017: Сделан break; заявления работают правильно
Обновление 28/01/2019: Положите for в макроимени так, что слово indexed является допустимым именем переменной. Я сомневаюсь, что for_indexed вызовет какие-либо конфликты.

Ответ 3

Используйте range-v3. Range-v3 - это библиотека диапазонов следующего поколения, разработанная и реализованная членом комитета ISO C++ Эриком Ниблером и предназначенная для использования в стандарте C++ в будущем.

С помощью range-v3 OP проблему легко решить:

using ranges::v3::view::zip;
using ranges::v3::view::ints;

for(auto &&[i, idx]: zip(storedValues, ints(0u))){
    std::cout << idx << ": " << i.function() << '\n';
}

Вам понадобится компилятор, который поддерживает C++ 17 или более позднюю версию, чтобы скомпилировать этот фрагмент кода, не только для синтаксиса структурированной привязки, но и для факта, что возвращаемый тип функций begin и end для возвращаемого значения ranges::v3::view::zip отличаются.

Вы можете увидеть онлайн пример здесь. Документация по range-v3 находится здесь, а сам исходный код размещен здесь. Вы также можете посмотреть здесь, если вы используете компиляторы MSVC.

Ответ 4

Это довольно просто, честно говоря, просто нужно выяснить, что вы можете вычесть адреса :)

& i будет ссылаться на адрес в памяти, и он будет увеличиваться на 4 от индекса к индексу, потому что он содержит целое число из определенного типа вектора. Теперь & values [0] ссылается на первую точку, когда вы вычитаете 2 адреса, разница между ними будет 0,4,8,12 соответственно, но в действительности это вычитание размера целочисленного типа, который обычно составляет 4 байта. Таким образом, в соответствии 0 = 0-й int, 4 = 1-й int, 8 = 2-й int, 12 = 3-й int

Вот оно в векторе

vector<int> values = {10,30,9,8};

for(auto &i: values) {

cout << "index: " <<  &i  - &values[0]; 
cout << "\tvalue: " << i << endl;

}

Вот это для обычного массива, почти то же самое

int values[]= {10,30,9,8};

for(auto &i: values) {

cout << "index: " <<  &i  - &values[0];
cout << "\tvalue: " << i << endl;

}

Обратите внимание, что это для С++ 11, если вы используете g++, не забудьте использовать параметр -std = С++ 11 для компиляции

Ответ 5

Здесь обновленная версия макропрограммы препроцессора для С++ 17. Clang Генерирует идентичный отладочный и оптимизированный код по сравнению с написанием цикла с индексом вручную. MSVC генерирует идентичный оптимизированный код, но добавляет несколько дополнительных инструкций при отладке.

#define for_index(...) for_index_v(i, __VA_ARGS__)
#define for_index_v(i, ...) if (size_t i##_next = 0; true) for (__VA_ARGS__) if (size_t i = i##_next++; true)

Я пытался покопаться в дополнительном фрагменте кода, который MSVC добавлял в отладочные сборки, я не совсем уверен, какова его цель. В начале цикла добавляется следующее:

        xor     eax, eax
        cmp     eax, 1
        je      [email protected]_i

Который пропустит цикл полностью. Используемый пример: https://godbolt.org/z/VTWhgT