使用基于范围的 for 循环时需要迭代器

目前,我只能用这个做远程循环:

for (auto& value : values)

但是有时候我需要一个值的迭代器,而不是一个引用(不管是什么原因)。有没有一种方法不需要通过整个向量来比较值?

54871 次浏览

Use the old for loop as:

for (auto it = values.begin(); it != values.end();  ++it )
{
auto & value = *it;
//...
}

With this, you've value as well as iterator it. Use whatever you want to use.


EDIT:

Although I wouldn't recommended this, but if you want to use range-based for loop (yeah, For whatever reason :D), then you can do this:

 auto it = std::begin(values); //std::begin is a free function in C++11
for (auto& value : values)
{
//Use value or it - whatever you need!
//...
++it; //at the end OR make sure you do this in each iteration
}

This approach avoids searching given value, since value and it are always in sync.

range based for loop is created as the c++ counterpart for foreach in java that allows easy iteration of array elements. It is meant for removing the usage of complex structures like iterators so as to make it simple. I you want an iterator, as Nawaz said, you will have to use normal for loop.

Here is a proxy wrapper class to allow you to expose the hidden iterator by aliasing it to your own variable.

#include <memory>
#include <iterator>


/*  Only provides the bare minimum to support range-based for loops.
Since the internal iterator of a range-based for is inaccessible,
there is no point in more functionality here. */
template< typename iter >
struct range_iterator_reference_wrapper
: std::reference_wrapper< iter > {
iter &operator++() { return ++ this->get(); }
decltype( * std::declval< iter >() ) operator*() { return * this->get(); }
range_iterator_reference_wrapper( iter &in )
: std::reference_wrapper< iter >( in ) {}
friend bool operator!= ( range_iterator_reference_wrapper const &l,
range_iterator_reference_wrapper const &r )
{ return l.get() != r.get(); }
};


namespace unpolluted {
/*  Cannot call unqualified free functions begin() and end() from
within a class with members begin() and end() without this hack. */
template< typename u >
auto b( u &c ) -> decltype( begin( c ) ) { return begin( c ); }
template< typename u >
auto e( u &c ) -> decltype( end( c ) ) { return end( c ); }
}


template< typename iter >
struct range_proxy {
range_proxy( iter &in_first, iter in_last )
: first( in_first ), last( in_last ) {}


template< typename T >
range_proxy( iter &out_first, T &in_container )
: first( out_first ),
last( unpolluted::e( in_container ) ) {
out_first = unpolluted::b( in_container );
}


range_iterator_reference_wrapper< iter > begin() const
{ return first; }
range_iterator_reference_wrapper< iter > end()
{ return last; }


iter &first;
iter last;
};


template< typename iter >
range_proxy< iter > visible_range( iter &in_first, iter in_last )
{ return range_proxy< iter >( in_first, in_last ); }


template< typename iter, typename container >
range_proxy< iter > visible_range( iter &first, container &in_container )
{ return range_proxy< iter >( first, in_container ); }

Usage:

#include <vector>
#include <iostream>
std::vector< int > values{ 1, 3, 9 };


int main() {
// Either provide one iterator to see it through the whole container...
std::vector< int >::iterator i;
for ( auto &value : visible_range( i, values ) )
std::cout << "# " << i - values.begin() << " = " << ++ value << '\n';


// ... or two iterators to see the first incremented up to the second.
auto j = values.begin(), end = values.end();
for ( auto &value : visible_range( j, end ) )
std::cout << "# " << j - values.begin() << " = " << ++ value << '\n';
}

I tried myself on this and found a solution.

Usage:

for(auto i : ForIterator(some_list)) {
// i is the iterator, which was returned by some_list.begin()
// might be useful for whatever reason
}

The implementation was not that difficult:

template <typename T> struct Iterator {
T& list;
typedef decltype(list.begin()) I;


struct InnerIterator {
I i;
InnerIterator(I i) : i(i) {}
I operator * () { return i; }
I operator ++ () { return ++i; }
bool operator != (const InnerIterator& o) { return i != o.i; }
};


Iterator(T& list) : list(list) {}
InnerIterator begin() { return InnerIterator(list.begin()); }
InnerIterator end() { return InnerIterator(list.end()); }
};
template <typename T> Iterator<T> ForIterator(T& list) {
return Iterator<T>(list);
}

There is a very simple way of doing this for std::vector, which should also work if you are resizing the vector during the process (I'm not sure whether the accepted answer considers this case)

If b is your vector, you can just do

for(auto &i:b){
auto iter = b.begin() + (&i-&*(b.begin()));
}

where iter will be your required iterator.

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

Let's do it very dirty ... I know, the 0x70h is changing with stack-usage, compiler version, .... It should be exposed by the compiler, but it is not :-(

char* uRBP = 0; __asm { mov uRBP, rbp }
Iterator** __pBegin = (Iterator**)(uRBP+0x70);
for (auto& oEntry : *this) {
if (oEntry == *pVal) return (*__pBegin)->iPos;
}

Late as always :), but I'm here.

C++20 introduces syntax for the initializer-statement in range-based for loops. This initialization may either a simple-declaration, or an expression-statement. (The current working draft of C++23 also makes it possible to write an type-alias-declaration instead).

For a iterator, or a index, simply do something similar like the following:

std::vector<int> vec;


for (auto it = vec.begin(); auto& elem: vec) {
// ...
it++;
}


for (int i = 0; auto& elem: vec) {
// ...
i++;
}

This fixes the issue of scope of the outside variable method which @nawaz mentioned.

To note: expressions of that sort aren't limited to only one initialization, and there are also plenty of cool things that can be done inline. Examples:

// This will only be useful for containing a complex typedef's scope inside
// a for-loop, and I would say, is a smell that your typing system is not too
// developed.
for(typedef std::vector<std::vector<int>> Matrix; Matrix& m: container) {
// ...
}


// Good old (or rather, very new) one liner.
for(MyType my_instance(x,y,z); auto& elem: my_instance) {
// ...
}

Boost has a very nice range adaptor indexed:

  #include <boost/range/adaptors.hpp>
std::vector<std::string> list = {"boost", "adaptors", "are", "great"};
for (auto v: list | boost::adaptors::indexed(1)) {
printf("%ld: %s\n", v.index(), v.value().c_str());
}

The output:

1: boost
2: adaptors
3: are
4: great

Here we index from 1 (boost::adaptors::indexed(1)), but we could easily index from any other value also.

It is an index, not an iterator, but the most common usages of the iterator are

  • converting it into index for accessing an item in another vector.
  • reporting/returning the position where something happened.
  • accessing some neighbouring value like previous.
  • decorating the output of some kind.

All this can also be done with index directly. From the other side, passing the iterator from inside the loop somewhere else to use as exactly iterator looks like rather complicated approach.