Access index in range-for loop

后端 未结 6 453
忘了有多久
忘了有多久 2020-12-24 11:44

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

        
相关标签:
6条回答
  • 2020-12-24 12:13

    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.

    0 讨论(0)
  • 2020-12-24 12:13

    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)

    0 讨论(0)
  • 2020-12-24 12:16

    You can use the enumerate view of range-v3:

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

    In 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;
    }
    
    0 讨论(0)
  • 2020-12-24 12:20

    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

    0 讨论(0)
  • 2020-12-24 12:21

    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.

    0 讨论(0)
  • 2020-12-24 12:36

    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

    0 讨论(0)
提交回复
热议问题