48

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?

  • 5
    Why do you think incrementing a counter variable is *very* inefficient? – Benjamin Bannier Sep 12 '12 at 23:54
  • @honk Perhaps a poor choice of words. I thought there might be something that is more efficient. –  Sep 13 '12 at 00:00
  • 2
    @Kyryx This definitely falls into the category of micro-optimizations. Never do micro-optimizations unless you are 100% sure that they are actually required (because in many cases, they can actually hurt the overall performance). – Šimon Tóth Sep 13 '12 at 00:12
  • 1
    I do not have access to C++11 compiler right now - but wouldn't it work like this: `cout << (i - storedValues.begin()) << ':' << i.function();` Just guess - do not know for sure. – PiotrNycz Sep 13 '12 at 00:44
  • @PiotrNycz: No. `i` is a value from the array. It's a `thisObject`. And even if it were a `thisObject*`, there's no guarantee that `vector::begin` returns a *pointer*. – Nicol Bolas Sep 13 '12 at 01:16
  • @NicolBolas I get your point - `std::vector::begin()` as an example. Just curious if for "ordinary" vectors this would work: `cout << (&i - &storedValuesp[0]) << ':' << i.function();` – PiotrNycz Sep 13 '12 at 01:31
  • @PiotrNycz: The standard doesn't require it. – Nicol Bolas Sep 13 '12 at 01:34
  • 5
    Before even considering the performance impact of a counter, the range based for loop that you use is making *copies* of the elements, you will be better of using references and incrementing a counter, or using the good ol' for loop... – David Rodríguez - dribeas Sep 13 '12 at 02:22

8 Answers8

54

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.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 5
    Thank you for the response. I got a little overexcited with the range-based loops. –  Sep 13 '12 at 00:02
  • 2
    Do you usually save the end like that? The loop invariant optimization should take care of any issues you have calling size() alot, in a modern compiler. – David Sep 13 '12 at 20:07
  • @Dave: Maybe, maybe not. Sometimes I prefer being explicit about this, and moreover it's a uniform style that I can use in any situation (e.g. for iterators). – Kerrek SB Sep 13 '12 at 21:24
  • 2
    Range-based for loops have nothing to do with collections. They manipulate each element _of the range_. That said, if the iterators defining the range are at least _ForwardIterator_-s, then the _distance_ between the beginning of the range and the actual position is also well-defined. – Bulletmagnet Sep 18 '15 at 08:23
  • @David: Another use case for a loop header like this is that it lends itself readily to reverse iteration by defining `std::size_t ri = e - i - 1;` inside the loop body. That's handy occasionally. – Kerrek SB Sep 18 '15 at 08:48
28

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;
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
cbuchart
  • 10,847
  • 9
  • 53
  • 93
  • 12
    The C++20 additional initialization in range loops is a valuable addition – Moia Feb 13 '20 at 14:45
  • Unfortuntely, the thing with manual counting inside the loop is that `continue` doesn't work as expected. – oisyn Aug 30 '23 at 18:51
21

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)

Daira Hopwood
  • 2,264
  • 22
  • 14
ThatsJustCheesy
  • 1,370
  • 14
  • 24
  • 1
    Names starting with two underscores are reserved for the implementation. It is undefined behavior to use them yourself. – aschepler Apr 05 '17 at 00:53
  • I knew about that, but I wanted to use names that no programmer would sensibly use in their actual code. I'll change them. – ThatsJustCheesy Apr 05 '17 at 11:46
  • 1
    This is SO clever and answers SO perfectly to the question that it absolutely deserves to be the chosen answer. – Fabio A. May 25 '17 at 15:48
  • Only 3 `for`s are necessary if you define all variables in the first one: `#define for_with_index(V, ...) for (size_t V=0, _brk_=0, _j_, _i_=1; _i_; _i_=0) for (__VA_ARGS__) if (_brk_) break; else for (_brk_=1, _j_=1; _j_; _j_=0, _brk_=0, V++)` – Řrřola Dec 21 '19 at 12:50
  • Saved a variable: `#define for_with_index(V, ...) for (size_t V=0, _brk_=0, _i_=1; _i_; _i_=0) for (__VA_ARGS__) if (_brk_) break; else for (_brk_=1; _brk_; _brk_=0, V++)` – Řrřola Dec 21 '19 at 12:58
  • @Řrřola Cool! I always had a feeling that it wan't the optimal solution but never bothered to simplify ;) feel free to edit the answer if you want the rep points, or I can do it (w/ credit of course). – ThatsJustCheesy Dec 23 '19 at 01:42
  • `#define for_indexed_v(v, ...) if (std::size_t v = -1) for (__VA_ARGS__) if ((++v, true))` allows `break` and `continue` and works in c++11 with only 1 variable and probably optimises very well – Artyer Dec 21 '20 at 16:26
  • @Artyer ah yes, I had completely forgotten that declaring a variable in `if` is valid. This is great, thank you! – ThatsJustCheesy Dec 23 '20 at 17:36
  • @ThatsJustCheesy could you post the lambda-based solution please? https://stackoverflow.com/questions/69008489/access-index-of-current-element-in-range-for-loop-c20 – Tom Huntington Sep 01 '21 at 06:01
16

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.

gnaggnoyil
  • 764
  • 5
  • 14
  • 3
    Funny that this feature exists in PHP since many years as `foreach($data as $key => $value) { /* ... */ }` or `foreach($data as $value) { /* ... */ }` if you don't want the index itaree. But you know everyone pee on this language... – Romain Laneuville Nov 14 '18 at 14:02
  • @RomainLaneuville Not familiar with PHP, but the cpp solution above is nothing more than a combination of some basic operation of ranges in `range-v3` library which can also be combined orthogonally to suit needs far more than a simple index accessing. And `range-v3` itself is just a library avaliable from C++11 though two C++17's language features are also needed to make the solution above look nicer. – gnaggnoyil Jan 31 '19 at 09:09
  • 2
    @gnaggnoyil what RomainLaneuville is saying is that getting the index of an array in a foreach loop (range loop) is something that has been present in PHP for years now. It's part of the language. No need for extra libs or newer versions – AntonioCS Jun 12 '19 at 16:47
4

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

  • 10
    This only works for tightly packed sequential containers like plain arrays or vectors. It will *not* work for linked lists, trees, maps, or containers in general. – ferry Dec 04 '18 at 15:22
3

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

  • 1
    The extra assembly sets eax to 0, compares it with 1 and then jumps if it's true - but it can never be true because 0 is not equal to 1. – Jerry Jeremiah May 14 '19 at 05:57
  • 2
    Ah, right, the two extra blocks are the invalid if statements. It seems gcc/clang have the ability to trim them out in debug builds and msvc doesn't. – Chris Savoie May 15 '19 at 06:09
  • They probably special-case constant conditions during code generation. – Trass3r Jul 24 '20 at 10:42
  • This is actually a quite nice and acceptable solution. And very efficient too unlike `enumerate` range adapter based solutions, even hand-written ones. – Trass3r Jul 24 '20 at 10:46
1

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);
    });
}
James
  • 11
  • 1
1

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
...
Julien-L
  • 5,267
  • 3
  • 34
  • 51