60

Often when iterating through a string (or any enumerable object), we are not only interested in the current value, but also the position (index). To accomplish this by using string::iterator we have to maintain a separate index:

string str ("Test string");
string::iterator it;
int index = 0;
for ( it = str.begin() ; it < str.end(); it++ ,index++)
{
    cout << index << *it;
}

The style shown above does not seem superior to the 'c-style':

string str ("Test string");
for ( int i = 0 ; i < str.length(); i++)
{
    cout << i << str[i] ;
}

In Ruby, we can get both content and index in a elegant way:

"hello".split("").each_with_index {|c, i| puts "#{i} , #{c}" }

So, what is the best practice in C++ to iterate through an enumerable object and also keep track of the current index?

hellow
  • 9,446
  • 5
  • 41
  • 61
pierrotlefou
  • 36,417
  • 33
  • 130
  • 168
  • Beware in the second block of code! str.length() is of type std::string::size_type, so you need to declare the variable i with std::string::size_type otherwise compiler gives you unnecessary warning. – User123 Feb 06 '21 at 19:53

7 Answers7

53

Like this:


    std::string s("Test string");
    std::string::iterator it = s.begin();

    //Use the iterator...
    ++it;
    //...

    std::cout << "index is: " << std::distance(s.begin(), it) << std::endl;
Leandro T. C. Melo
  • 3,852
  • 19
  • 22
47

I've never heard of a best practice for this specific question. However, one best practice in general is to use the simplest solution that solves the problem. In this case the array-style access (or c-style if you want to call it that) is the simplest way to iterate while having the index value available. So I would certainly recommend that way.

TheUndeadFish
  • 7,700
  • 1
  • 20
  • 16
24

You can use standard STL function distance as mentioned before

index = std::distance(s.begin(), it);

Also, you can access string and some other containers with the c-like interface:

for (i=0;i<string1.length();i++) string1[i];
Andrew
  • 2,189
  • 4
  • 26
  • 41
12

A good practice would be based on readability, e.g.:

string str ("Test string");
for (int index = 0, auto it = str.begin(); it < str.end(); ++it)
   cout << index++ << *it;

Or:

string str ("Test string");
for (int index = 0, auto it = str.begin(); it < str.end(); ++it, ++index)
   cout << index << *it;

Or your original:

string str ("Test string");
int index = 0;
for (auto it = str.begin() ; it < str.end(); ++it, ++index)
   cout << index << *it;

Etc. Whatever is easiest and cleanest to you.

It's not clear there is any one best practice as you'll need a counter variable somewhere. The question seems to be whether where you define it and how it is incremented works well for you.

Alex Reynolds
  • 91,635
  • 50
  • 223
  • 320
  • 2
    My previous edit is correct, @Alex Reynolds. Check [It's not possible to declare two variables of different types in the initialization body of a `for` loop in `C++`.](https://stackoverflow.com/questions/2687392/is-it-possible-to-declare-two-variables-of-different-types-in-a-for-loop) and [this](https://ideone.com/lngBfo). – Zaid Khan Oct 01 '17 at 11:32
3

For strings, you can use string.c_str() which will return you a const char*, which can be treated as an array, example:

const char* strdata = str.c_str();

for (int i = 0; i < str.length(); ++i)
    cout << i << strdata[i];
jscharf
  • 5,480
  • 2
  • 20
  • 16
  • This question is similar to my own so I would rather ask here, how do you compare the value of stardata[i] to " "? Is there a way to convert it to char? – frogeyedpeas Apr 11 '13 at 14:01
3

I would use it-str.begin() In this particular case std::distance and operator- are the same. But if container will change to something without random access, std::distance will increment first argument until it reach second, giving thus linear time and operator- will not compile. Personally I prefer the second behaviour - it's better to be notified when you algorithm from O(n) became O(n^2)...

maxim1000
  • 5,954
  • 1
  • 20
  • 18
2

Since std::distance is only constant time for random-access iterators, I would probably prefer explicit iterator arithmetic. Also, since we're writing C++ code here, I do believe a more C++ idiomatic solution is preferable over a C-style approach.

string str{"Test string"};
auto begin = str.begin();

for (auto it = str.begin(), end = str.end(); it != end; ++it)
{
    cout << it - begin << *it;
}
BartoszKP
  • 32,105
  • 13
  • 92
  • 123
user2015735
  • 349
  • 2
  • 5