Find element position in C ++ 11 for loop?

Suppose I have the following code:

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

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

+70
c ++ iterator c ++ 11
Jun 09 '12 at 15:37
source share
11 answers

Yes, you can, just take a little massage;)

The trick is to use the composition: instead of iterating over the container directly, you "zip" its 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 on ideone , although it lacks for-range loop support, so it is less cute.

EDIT:

Just remembered that I should check Boost.Range more often. Unfortunately, the zip range was not found, but I found perl: boost::adaptors::indexed . However, access to the iterator requires access to the index. Shame: x

Otherwise, with counting_range and a common zip I am sure that something interesting could be done ...

In an ideal world, I would suggest:

 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 in the form of a set of link tuples and iota(0) just creating a “false” range that starts at 0 and just counts to infinity (or, well, the maximum of its type ...).

+61
Jun 09 '12 at 16:20
source share

jrok is right: ranges for loops are not for this purpose.

However, in your case, it can be calculated 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 obviously bad practice, as it confuses the code and makes it more fragile (it breaks easily if someone changes the container type, overloads the & operator or replaces “auto &” with “auto. Debug it!)

NOTE. Continuity is guaranteed for a vector in C ++ 03, and an array and string in the C ++ 11 standard.

+27
Jun 09 '12 at 16:01
source share

No, you cannot (at least not without effort). If you need an item position, you should not use a range. Remember that this is just a convenient tool for the most common case: execute the code for each element. In less common cases where you need the position of an element, you should use the less convenient regular for loop.

+18
Jun 09 '12 at 16:00
source share

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 you need to set -std=c++1y . The reason you need C ++ 14 is because auto parameters in a lambda function.

+10
Jul 03 '14 at 17:14
source share

Based on @Matthieu's answer, there is a very elegant solution using the boost :: adapters :: indexed mentioned above:

 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 works in much the same way as the “perfect world solution” mentioned, has beautiful syntax and is concise. Please note that the el type in this case is something like boost::foobar<const std::string&, int> , so it processes the link there and copying is not performed. It’s even incredibly efficient: https://godbolt.org/g/e4LMnJ (the code is equivalent to storing your own counter variable, which is as good as possible)

For completeness, alternatives:

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

Or using the continuous property of the vector:

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

The former generates the same code as the boost adapter version (optimally), and the latter is 1 instruction longer: https://godbolt.org/g/nEG8f9

Note: if you want to know if you have the last item 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 you need to use auto& / auto const& (as described above), but it is recommended anyway. Depending on the input, this can also be pretty fast (especially when the compiler knows the size of your vector)

Replace &foo with std::addressof(foo) to be safe for generic code.

+9
Jul 20 '18 at 8:44
source share

If you insist on using an index-based range and know the index, it is fairly simple to maintain the index as shown below. I don’t think there is a cleaner / simpler solution for loop based ranges. But why not use the standard for (;;)? This will probably make your intent and code clearer.

 vector<int> list; int idx = 0; for(auto& elem:list) { int i = elem; //TODO whatever made you want the idx ++idx; } 
+4
Aug 17 '16 at 15:21
source share

There is a surprisingly easy way to do this.

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

where i will be your required index.

This exploits the fact that C ++ vectors are always contiguous .

+3
Apr 15 '17 at 7:06 on
source share

I read from your comments that one of the reasons you want to know the index is to know if this 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 means that the container skips the delimiter 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 } 
+2
Nov 13 '13 at 3:13
source share

Here is a macro-based solution that is probably superior to most others in terms of simplicity, compilation time and quality of code generation:

 #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 ! 
+1
Jan 12 '19 at 3:53
source share

Tobias Widlund wrote a nice enumerated header only in the Python style for MIT (although C ++ 17):

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 } 
0
Feb 14 '19 at 9:11
source share

If you want to avoid the need to write a helper function with an 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; }); } 
0
Aug 27 '19 at 13:39 on
source share



All Articles