Daniel Schranz
Daniel Schranz

Reputation: 21

C++ iterate over iterator

I am currently "modernizing" my toy-compiler code to use as many C++11 features as possible to learn how to write modern C++. While looking at my code, searching for things to modernise I found this construct:

for (size_t i = 0; i < _vector.size(); i++) {
   for (size_t j = 0; j < _vector[i].length(); j++) {

_vector is a std::vector<std::string> containing the source code of the file which needs to be compiled. Each std::string in the vector is a line of code which needs to be compiled. Before compilation takes place the compiler checks whether the code to be compiled has any syntax errors, and the first step of this check is to look out for missing semicolons. I do this by looking at each character one by one and checking if said character is a semicolon. If it is, I perform a check whether the semicolon is needed by building a kind of mini-syntax-tree and thereby determining if it is necessary. I currently access each character with _vector[i][j] which is very reminiscent of ANSI-C (this is how you access each character in an array of char*) and I want to replace this with iterators. As far as I know, an Iterator is a pointer-like construct which points to an element in a container/sequence/whatever. Applying the pointer analogy I deduced that an std::vector<std::string>::iterator points to a string in a vector, therefore a theoretical std::vector<std::string>::iterator::iterator would point to a single character in a string object. But because there isn't such thing in the STL the pointer analogy isn't of much use here. So my question is: How do I access each Character within a std::vector<std::string>::iterator?

Upvotes: 0

Views: 16408

Answers (3)

Brambor
Brambor

Reputation: 664

When using for(x : y) syntax, you don't have to use auto, you can be more explicit. In this case I find the code easier to read when I know what types are being used.

// std::vector<std::string> _vector;
for (const std::string &line : _vector) {
   for (const char ch : line) {

Is much more telling than:

for (const auto &line : _vector) {
   for (const auto ch : line) {

I advise against using auto in most cases. (One exception is iterators.)

Note: Do NOT use const char&, that would add a level of indirection for no benefit. By that, the compiler makes an extra pointer (32-bit/64-bit number) to an 8-bit char instead of directly working with a copy of the 8-bit char. The indirection means extra dereferencing.

The following example takes this further:

Use string_view instead for better performance [1].

for (std::string_view line : _vector) {
    for (const char ch : line) {

Upvotes: 0

Olaf Dietsche
Olaf Dietsche

Reputation: 74008

An iterator behaves like a pointer. So in your case, you can dereference the first iterator to get the string and use a std::string::iterator to access each character.

Another way is to use operator->() to directly get at the std::string::iterator, e.g.

for (std::vector<std::string>::iterator i = _vector.begin(); i != _vector.end(); ++i) {
    for (std::string::iterator j = i->begin(); j != i->end(); ++j) {
        /* ... */
    }
}

With C++11, you can simplify this to

for (auto i = _vector.begin(); i != _vector.end(); ++i) {
    for (auto j = i->begin(); j != i->end(); ++j) {
        /* ... */
    }
}

As @LightnessRacesinOrbit mentioned and @MateuszGrzejek already showed, the next step would be moving to a Range-based for loop. A loop iterating from the first begin() to the last end() element

for (auto i = _vector.begin(); i != _vector.end(); ++i) {
    // Do something with string (*i)

can be written as

for (auto &s : _vector) {
    // Do something with string (s)

or, if you don't modify string, make it const

for (const auto &s : _vector) {
    // Do something with string (s)

Upvotes: 3

Mateusz Grzejek
Mateusz Grzejek

Reputation: 12048

If you have access to full C++ 11 support, why bother with explicit iterator types? That's where auto comes in:

std::vector<std::string> sv;
//fill vector...

for(const auto& s : sv)
{
    for(const auto& c : s)
    {
        //'c' represents current character
    }
}

We use range-base for loop here. In first loop, s is a const reference (*) to current string from vector. Since std::string has support for iteration semantics, we can also iterate over it using for. c is a const reference to current character in string (s).

Live demo: click.


(*) Since your loop seems to be read-only, const reference should be used. If you want to perform any modification, just change this from const auto& to auto&.

Upvotes: 8

Related Questions