102

Assume I have the following code:

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

Can I find the position of elem in the vector without maintaining a separate iterator?

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
Fred Finkle
  • 1,947
  • 3
  • 18
  • 21
  • 18
    That's not what range-based for is for (heh, is that a pun?) – jrok Jun 09 '12 at 15:42
  • 2
    This is not possible in STL containers, unless using `std::find` or some other overkill function. You can't conclude iterators from contained elements. Why not maintain an iterator? – Eitan T Jun 09 '12 at 15:49
  • 2
    For two reasons. The first is all I want to do (in this case) is see whether I'm at the last element or not :) and the second is that the compiler must be maintaining one, why can't I access it? "this" is a variable with scope maintained by the compiler, why not here? Or provide an alternative (but still convenient) syntax that, as javascript does, sets up a variable that changes as the you go through the loop. for(auto& index:list) – Fred Finkle Jun 09 '12 at 19:25
  • 1
    @FredFinkle you are actually correct, [there is an iterator](http://en.cppreference.com/w/cpp/language/range-for), but when using a range based `for` loop, it is a compiler-internal name and can therefore not be used in your code. So if you really want to know if you're at the last element, you should use the `for(;;)` loop. – iFreilicht Jul 31 '14 at 16:11
  • Related: [https://stackoverflow.com/q/28769156/364696](https://stackoverflow.com/q/28769156/364696) – ShadowRanger Jan 18 '18 at 03:22
  • The header-only [cppitertools](https://github.com/ryanhaining/cppitertools) library implements this. – Romeo Valentin Jun 04 '21 at 11:58
  • 1
    [std::views::enumerate](https://en.cppreference.com/w/cpp/ranges/enumerate_view) is a C++23 solution. – Steve Ward Aug 09 '23 at 01:44

13 Answers13

70

Yes you can, it just take some massaging ;)

The trick is to use composition: instead of iterating over the container directly, you "zip" it with an index along the way.

Specialized zipper code:

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); }

And using it:

#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";
    }
}

You can see it at ideone, though it lacks the for-range loop support so it's less pretty.

EDIT:

Just remembered that I should check Boost.Range more often. Unfortunately no zip range, but I did found a pearl: boost::adaptors::indexed. However it requires access to the iterator to pull of the index. Shame :x

Otherwise with the counting_range and a generic zip I am sure it could be possible to do something interesting...

In the ideal world I would imagine:

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";
    }
}

With zip automatically creating a view as a range of tuples of references and iota(0) simply creating a "false" range that starts from 0 and just counts toward infinity (or well, the maximum of its type...).

einpoklum
  • 118,144
  • 57
  • 340
  • 684
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 3
    How about `counting_range` (or [`boost::counting_iterator`](http://www.boost.org/libs/iterator/doc/counting_iterator.html)) + [`boost::zip_iterator`](http://www.boost.org/libs/iterator/doc/zip_iterator.html)? – ildjarn Jun 09 '12 at 19:58
  • 1
    @ildjarn: Yes, Boost.Iterators has the building blocks (it seems), however there is no corresponding range, which is annoying. – Matthieu M. Jun 10 '12 at 10:00
  • @Xeo Your version works fine for lvalues (indeed as you said no copies take place). For rvalues there is some problem, though. I have not spotted it yet, but I will continue to look into it tomorrow. Basically, when I use `index` like this `for (auto x : index(std::vector{2, 4, 6})) { ... }` I get this error: `error: no matching function for call to ‘Indexer > >::iterator::iterator(std::vector >::const_iterator)’`. I used g++-4.7. – betabandido Jul 04 '12 at 23:06
  • @betabandido: Yeah, that's why I didn't roll back yet and asked for Matthieu to join me in the Lounge, to discuss that exact problem. `begin` and `end` are `const`, and if the original argument is an rvalue, `_container` is a value type and is also `const`, making `_container.begin()` and `_container.end()` return `const_iterator`s instead of the wanted `iterator`s. One solution is to add non-`const` `begin` and `end` functions to the `Indexer`. – Xeo Jul 05 '12 at 00:22
  • @Xeo: sorry but my hours differ slightly from yours it seems. Indeed in this case I think that removing the `const` from `begin` and `end` would be the right thing to do. – Matthieu M. Jul 05 '12 at 06:14
  • You were on the right track. See my answer for a full use of `boost::adaptors::indexed` together with a performance evaluation. – Flamefire Jul 20 '18 at 08:45
  • I really like this idiomatic solution! – Sven Hager May 28 '21 at 18:45
35

jrok is right : range-based for loops are not designed for that purpose.

However, in your case it is possible to compute it using pointer arithmetic since vector stores its elements contiguously (*)

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]); 

}

But this is clearly a bad practice since it obfuscates the code & makes it more fragile (it easily breaks if someone changes the container type, overload the & operator or replace 'auto&' by 'auto'. good luck to debug that!)

NOTE: Contiguity is guaranteed for vector in C++03, and array and string in C++11 standard.

tshepang
  • 12,111
  • 21
  • 91
  • 136
Frédéric Terrazzoni
  • 2,190
  • 19
  • 25
23

No, you can't (at least, not without effort). If you need the position of an element, you shouldn't use range-based for. Remember that it's just a convenience tool for the most common case: execute some code for each element. In the less-common circumstances where you need the position of the element, you have to use the less-convenient regular for loop.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
23

Based on the answer from @Matthieu there is a very elegant solution using the mentioned boost::adaptors::indexed:

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;
}

You can try it

This works pretty much like the "ideal world solution" mentioned, has pretty syntax and is concise. Note that the type of el in this case is something like boost::foobar<const std::string&, int>, so it handles the reference there and no copying is performed. It is even incredibly efficient: https://godbolt.org/g/e4LMnJ (The code is equivalent to keeping an own counter variable which is as good as it gets)

For completeness the alternatives:

size_t i = 0;
for(auto const& el: strings) {
  std::cout << i << ": " << el << std::endl;
  ++i;
}

Or using the contiguous property of a vector:

for(auto const& el: strings) {
  size_t i = &el - &strings.front();
  std::cout << i << ": " << el << std::endl;
}

The first generates the same code as the boost adapter version (optimal) and the last is 1 instruction longer: https://godbolt.org/g/nEG8f9

Note: If you only want to know, if you have the last element you can use:

for(auto const& el: strings) {
  bool isLast = &el == &strings.back();
  std::cout << isLast << ": " << el << std::endl;
}

This works for every standard container but auto&/auto const& must be used (same as above) but that is recommended anyway. Depending on the input this might also be pretty fast (especially when the compiler knows the size of your vector)

Replace the &foo by std::addressof(foo) to be on the safe side for generic code.

Flamefire
  • 5,313
  • 3
  • 35
  • 70
  • I added the 2 alternatives with godbolt comparison of the generated code for completeness and also addressed the need of the OP (in the comments) for detecting the last element – Flamefire Jul 20 '18 at 12:16
  • 1
    Maybe worth mentioning: Instead of `strings | boost::adaptors::indexed(0)` one can also use `boost::adaptors::index(strings)`. The outcome is the same but IMO it is a bit more intuitive. – luator Oct 27 '22 at 07:48
11

If you have a compiler with C++14 support you can do it in a functional style:

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

Works with clang 3.4 and gcc 4.9 (not with 4.8); for both need to set -std=c++1y. The reason you need c++14 is because of the auto parameters in the lambda function.

Michael
  • 1,133
  • 10
  • 11
  • 4
    `std::function` uses type erasure which is expensive. Why not use `template void for_enum(T& container, Callable op)` so you don't have to pay for type erasure? – NathanOliver Nov 29 '18 at 15:29
7

If you insist on using range based for, and to know index, it is pretty trivial to maintain index as shown below. I do not think there is a cleaner / simpler solution for range based for loops. But really why not use a standard for(;;)? That probably would make your intent and code the clearest.

vector<int> list;
int idx = 0;
for(auto& elem:list) {
    int i = elem;
    //TODO whatever made you want the idx
    ++idx;
}
Jens Winslow
  • 71
  • 1
  • 2
7

There is a surprisingly simple way to do this

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

where i will be your required index.

This takes advantage of the fact that C++ vectors are always contiguous.

Community
  • 1
  • 1
pulsejet
  • 1,101
  • 13
  • 27
7

Here's a quite beautiful solution using c++20:

#include <array>
#include <iostream>
#include <ranges>

template<typename T>
struct EnumeratedElement {
    std::size_t index;
    T& element;
};

auto enumerate(std::ranges::range auto& range) 
    -> std::ranges::view auto 
{
    return range | std::views::transform(
        [i = std::size_t{}](auto& element) mutable {
            return EnumeratedElement{i++, element};
        }
    );
}

auto main() -> int {
    auto const elements = std::array{3, 1, 4, 1, 5, 9, 2};
    for (auto const [index, element] : enumerate(elements)) {
        std::cout << "Element " << index << ": " << element << '\n';
    }
}

The major features used here are c++20 ranges, c++20 concepts, c++11 mutable lambdas, c++14 lambda capture initializers, and c++17 structured bindings. Refer to cppreference.com for information on any of these topics.

Note that element in the structured binding is in fact a reference and not a copy of the element (not that it matters here). This is because any qualifiers around the auto only affect a temporary object that the fields are extracted from, and not the fields themselves.

The generated code is identical to the code generated by this (at least by gcc 10.2):

#include <array>
#include <iostream>
#include <ranges>

auto main() -> int {
    auto const elements = std::array{3, 1, 4, 1, 5, 9, 2};
    for (auto index = std::size_t{}; auto& element : elements) {
        std::cout << "Element " << index << ": " << element << '\n';
        index++;
    }
}

Proof: https://godbolt.org/z/a5bfxz

Björn Sundin
  • 691
  • 7
  • 10
  • OMG, what is happening to the C/C++ I grew up with? This is nearly incomprehensible. – AlainD Jan 22 '21 at 10:51
  • C++98 is not the same language as C++20. Rust is incomprehensible by someone who only knows C. – Björn Sundin Jan 22 '21 at 11:12
  • Perhaps I've been programming in C, C++03 (and more recently C++11) for too long, but these lambdas, new obscure `auto main() -> int` syntax, type deduction with `auto` and so on is turning a once clean and beautiful language into a Rube Goldberg mess. Very clever, super impressive...and nearly incomprehensible. – AlainD Jan 22 '21 at 11:23
  • 4
    It is a matter of what you're used to. This is more comprehensible to me because this is the code I've written for the past year. I have chosen what features to use and when based purely on reasoning about safety and utility. For me it's like learning a new language with potential of better performance, safety and simplicity (abstraction). – Björn Sundin Jan 22 '21 at 11:31
  • Why does adding a view filter to the container in your example result in the output indices becoming `1`, `3`, `5`, `7`, `9`, `11`, `13` (instead of `0`, `1`, `2`, `3`, `4`, `5`, `6`)? Even a do-nothing filter has this effect. For example: `enumerate(elements) | std::views::filter([](auto const &) { return true; })` – Spire May 08 '21 at 17:17
  • @Spire This is probably because the begin iterator in ``filter_view`` is precalculated with ``find_if``, causing an initial dreference. The index is incremented every time the dereference operator is used on iterators from this enumerate function. A better method would be to write our own enumerate view class that only increments the index when the increment operator is called. Another alternative could be to use ``std::views::iota`` together with ``std::views::transform`` and restricting the function to random access ranges or something similar. – Björn Sundin May 10 '21 at 08:25
  • Not implemented in Clang [as of now](https://godbolt.org/z/rqzsdjWx7). MSVC [works](https://godbolt.org/z/qv1q87bxq). – tejasvi88 Feb 06 '22 at 09:46
2

I read from your comments that one reason you want to know the index is to know if the element is the first/last in the sequence. If so, you can do

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: For example, this prints a container skipping a separator in the last element. Works for most containers I can imagine (including arrays), (online demo 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
}
alfC
  • 14,261
  • 4
  • 67
  • 118
  • Please note (before unjustified downvoting) that the author of the question is asking this in the context of detecting the last element in a for-ranged loop for a container. For that I see no reason why comparing `&elem` and `&*std::prev(std::end(list))` will not work or be practical. I agree with the other answer that an iterator-based for is more appropriate for this, but still. – alfC Aug 04 '14 at 09:00
  • It seems easier to declare `int i=c.size();` before the loop and test `if(--i==0)`. – Marc Glisse Nov 08 '14 at 20:03
  • @MarcGlisse, the `int i` code was just an example. I will remove it to avoid confusion. Even if you use `size` before the loop you will need a counter. – alfC Nov 08 '14 at 20:09
2

Tobias Widlund wrote a nice MIT licensed Python style header only enumerate (C++17 though):

GitHub

Blog Post

Really nice to use:

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

for(auto [i, my_element] : en::enumerate(my_vector))
{
    // do stuff
}
Ken Y-N
  • 14,644
  • 21
  • 71
  • 114
M. Ahnen
  • 21
  • 3
1

Here's a macro-based solution that probably beats most others on simplicity, compile time, and code generation quality:

#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;
    }
}

Result:

$ g++ -o enumerate enumerate.cpp -std=c++11 && ./enumerate 
0 hello
1 world
2 !
Forrest Voight
  • 2,249
  • 16
  • 21
1

If you want to avoid having to write an auxiliary function while having the index variable local to the loop, you can use a lambda with a mutable variable.:

int main() {
    std::vector<char> values = {'a', 'b', 'c'};
    std::for_each(begin(values), end(values), [i = size_t{}] (auto x) mutable {
        std::cout << i << ' ' << x << '\n';
        ++i;
    });
}
1

You can use std::views::enumerate (C++23) to get the index of elements.

#include <iostream>
#include <ranges>
#include <vector>
 
int main()
{
    const auto letters = std::vector{'A', 'B', 'C', 'D', 'E'};
 
    for (const auto& [index, letter] : std::views::enumerate(letters))
    {
        std::cout << '(' << index << ':' << letter << ") ";
    }
}

Check the C++ 23 compiler support page to see if your compiler supports this feature.

Synck
  • 2,727
  • 22
  • 20