user1599559
user1599559

Reputation:

Access index in range-for loop

I have a vector of objects and am iterating through it using a range-for loop. I am using it to print a function from the object, like this:

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

But I want to print the index too. My desired output is:

1: value
2: value
//etc

I was going to just use a counter that I increased each time, but that seemed very inefficient. Is there a better way?

Upvotes: 52

Views: 52446

Answers (9)

Evin
Evin

Reputation: 11

In C++ 23 you can do the following (enumerate is now part of the standard library).

#include <iostream>
#include <vector>
#include <ranges>

int main()
{
    std::vector<char> letters = { 'a', 'b', 'c', 'd' };
    
    for (const auto &[index, letter] : std::views::enumerate(letters)) 
    {
        std::cout << index << " : " << letter << std::endl;
    }
}

Output:

0 : a
1 : b
2 : c
3 : d

Upvotes: 0

Julien-L
Julien-L

Reputation: 5416

Boost adaptor indexed:

#include <boost/range/adaptor/indexed.hpp>

std::vector<int> input {10,20,30,40,50,60,70,80,90};
for (const auto& element : input | boost::adaptors::indexed(0))
{
    std::cout << "Element = " << element.value()
              << " Index = " << element.index()
              << std::endl;
}

Output:

Element = 10 Index = 0
Element = 20 Index = 1
Element = 30 Index = 2
Element = 40 Index = 3
...

Upvotes: 3

ThatsJustCheesy
ThatsJustCheesy

Reputation: 1489

I created a preprocessor macro (greatly simplified by @Artyer) that handles this for you in a relatively clean way:

#define for_indexed(...) for_indexed_v(i, __VA_ARGS__)
#define for_indexed_v(v, ...) if (std::size_t v = -1) for (__VA_ARGS__) if ((++v, true))

Example usage:

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

To use a different loop variable:

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

The extra control flow logic should be optimized away in any non-debug builds. You're left with a relatively easy-to-read loop syntax.

2020 note: It would probably be more wise to use a lambda-based solution instead of macro trickery. Of course, the syntax wouldn't be as "clean", but it would have the advantage of being recognizable as actual C++ syntax. The choice is yours.

Update 2017/05/28: Made break; statements work correctly
Update 2019/01/28: Put for in the macro name so that the word indexed is a valid variable name. I doubt for_indexed will cause any conflicts.
Update 2020/12/23: Simplified drastically (thanks @Artyer)

Upvotes: 23

James
James

Reputation: 21

C++11 templated function that takes a lambda:

template<typename T, typename F>
void with_index(const T& range, F&& f) {
    std::size_t i = 0;
    for (auto it = std::begin(range); it != std::end(range); ++it)
    {
        f(*it, i++);
    }
}

Example generating random numbers for a set then iterating through it:

#include <cstdio>
#include <random>
#include <set>

struct A
{
    int x;

    friend constexpr bool operator<(const A& lhs, const A& rhs) {
        return lhs.x < rhs.x;
    }

    friend constexpr bool operator==(const A& lhs, const A& rhs) {
        return lhs.x == rhs.x;
    }
};

template<typename T, typename F>
void with_index(const T& range, F&& f) {
    std::size_t i = 0;
    for (auto it = std::begin(range); it != std::end(range); ++it)
    {
        f(*it, i++);
    }
}

int main()
{
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(0, 500);

    std::set<A> as;
    for (std::size_t i = 0; i < 100; ++i)
    {
        as.insert(A{dis(gen)});
    }

    with_index(as, [](const A& a, std::size_t i) {
        printf("%d %lu\n", a.x, i);
    });
}

Upvotes: 2

cbuchart
cbuchart

Reputation: 11555

You can use the enumerate view of range-v3:

std::vector<thisObject> storedValues;
for (auto const& [idx, value] : storedValues | ranges::views::enumerate) {
  std::cout << idx << ": " << value << '\n';
}

C++20 will introduce additional initializations in range-for loops:

std::vector<thisObject> storedValues;
for (size_t idx = 0; auto value : storedValues) {
  std::cout << idx << ": " << value << '\n';
  ++idx;
}

Upvotes: 40

Chris Savoie
Chris Savoie

Reputation: 109

Here's an updated version of the preprocessor macro version for c++17. Clang Generates identical debug and optimized code when compared to writing the loop with an index manually. MSVC generates identical optimized code, but adds a few extra instruction in debug.

#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)

I was trying to dig into the extra piece of code MSVC was adding in debug builds, I'm not quite sure what it's purpose is. It adds the following at the start of the loop:

        xor     eax, eax
        cmp     eax, 1
        je      $LN5@for_i

Which will skip the loop entirely. Example used: https://godbolt.org/z/VTWhgT

Upvotes: 3

Purifier Phoenix
Purifier Phoenix

Reputation: 67

It's pretty simple honestly, just got to figure out that you can subtract addresses :)

&i will reference the address in memory, and it will increment by 4 from index to index because it's hold a integer from the defined vector type. Now &values[0] references the first point, when you subtract the 2 addresses, the difference between the two will be 0,4,8,12 respectfully, but in reality its subtracting the size of the integer type which is usually 4 bytes. So in correspondence 0 = 0th int,4 = 1st int, 8 = 2nd int, 12 = 3rd int

Here it is in a vector

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

for(auto &i: values) {

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

}

Here it is for a regular array, pretty much the same thing

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

for(auto &i: values) {

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

}

Note this is for C++11, if you're using g++, remember to use -std=c++11 parameter for compiling

Upvotes: 5

gnaggnoyil
gnaggnoyil

Reputation: 764

Use range-v3. Range-v3 is the next generation range library designed and implemented by ISO C++ Committee member Eric Niebler, and is intended and expected to be merged in the C++ standard in the future.

By Using range-v3 OP's problem can be solved easily:

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

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

You will need a compiler that support C++17 or later to compile this piece of code, not only for the structured binding syntax, but also for the fact that the return type of begin and end function for the return value of ranges::v3::view::zip differ.

You can see the online example here. The documentation of range-v3 is here and the source code itself is hosted here. You can also have a look at here if you are using MSVC compilers.

Upvotes: 16

Kerrek SB
Kerrek SB

Reputation: 476990

You can't. The index is a specific notion to a vector, and not a generic property of a collection. The range-based loop on the other hand is a generic mechanism for iterating over every element of any collection.

If you do want to use the details of your particular container implementation, just use an ordinary loop:

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

To repeat the point: Range-based loops are for manipulating each element of any collection, where the collection itself doesn't matter, and the container is never mentioned inside the loop body. It's just another tool in your toolbox, and you're not forced to use it for absolutely everything. By contrast, if you either want to mutate the collection (e.g. remove or shuffle elements), or use specific information about the structure of the collection, use an ordinary loop.

Upvotes: 56

Related Questions