Why can't you use vector iterator to go through vector?

Pages: 12
Hey guys!

I'm currently getting myself used to using the more recent ways of making for loops with vectors and there is a little something I don't get.

Here is a bit of code I wrote to illustrate what I don't understand:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <string>
#include <vector>


int main()
{
    std::vector<std::string> str = { "one", "two", "three" };
    std::string number;
    
    std::cout << " Type in a number between 0 and 10 : ";
    std::cin >> number;
    
    bool number_matches = false;
    
    for (auto i = str.begin(); i != str.end(); ++i)
    {
        if (number == str[i])
        {
            number_matches = true;
            break;
        }
    }
    
    std::cout << "\n Is your number, one, two or three? : ";
    
    if (number_matches) std::cout << "Yes";
    else std::cout << "No";
}

Here's the error I'm getting:
 In function 'int main()':
18:26: error: no match for 'operator[]' (operand types are 'std::vector<std::basic_string<char> >' and '__gnu_cxx::__normal_iterator<std::basic_string<char>*, std::vector<std::basic_string<char> > >')
18:26: note: candidates are:
In file included from /usr/include/c++/4.9/vector:64:0,
                 from 3:
/usr/include/c++/4.9/bits/stl_vector.h:779:7: note: std::vector<_Tp, _Alloc>::reference std::vector<_Tp, _Alloc>::operator[](std::vector<_Tp, _Alloc>::size_type) [with _Tp = std::basic_string<char>; _Alloc = std::allocator<std::basic_string<char> >; std::vector<_Tp, _Alloc>::reference = std::basic_string<char>&; std::vector<_Tp, _Alloc>::size_type = long unsigned int]
       operator[](size_type __n) _GLIBCXX_NOEXCEPT
       ^
/usr/include/c++/4.9/bits/stl_vector.h:779:7: note:   no known conversion for argument 1 from '__gnu_cxx::__normal_iterator<std::basic_string<char>*, std::vector<std::basic_string<char> > >' to 'std::vector<std::basic_string<char> >::size_type {aka long unsigned int}'
/usr/include/c++/4.9/bits/stl_vector.h:794:7: note: std::vector<_Tp, _Alloc>::const_reference std::vector<_Tp, _Alloc>::operator[](std::vector<_Tp, _Alloc>::size_type) const [with _Tp = std::basic_string<char>; _Alloc = std::allocator<std::basic_string<char> >; std::vector<_Tp, _Alloc>::const_reference = const std::basic_string<char>&; std::vector<_Tp, _Alloc>::size_type = long unsigned int]
       operator[](size_type __n) const _GLIBCXX_NOEXCEPT
       ^
/usr/include/c++/4.9/bits/stl_vector.h:794:7: note:   no known conversion for argument 1 from '__gnu_cxx::__normal_iterator<std::basic_string<char>*, std::vector<std::basic_string<char> > >' to 'std::vector<std::basic_string<char> >::size_type {aka long unsigned int}'


So I'm guessing I can't use that iterator i to access the vector in the for loop, am I wrong in assuming so? And what's the point in this case if you have an iterator and you can't access anything within the vector with it?

I have the feeling I'm overlooking something quite obvious..

Any insight appreciated, thanks in advance!

Hugo.
You're using i like it's just an index, but it's an iterator. So it doesn't represent an index but a value and you get the value with the dereference operator *:

 
    if (number == *i)

Although it's more "modern" to write it like this:

1
2
3
4
5
    for (const auto& s: str)
        if (number == s) {
            number_matches = true;
            break;
        }

Last edited on
Thank you dutch, so if I get it, when I declare auto i = str.begin() in the first iteration of the for loop i is the equivalent of str[0]?

Oh, so when you declare for (const auto& s: std::vector) this is going through each member of the vector?

Would you have a link to some reference on how to use the above for loop format?
Last edited on
str.begin() is an iterator that "points" to str[0]. But it needs to be "dereferenced" to actually get str[0]. You should look up the implementation of an iterator to really understand what's going on.

And the "more modern" way I mentioned above is really just syntactic sugar that automates the loop from begin to end and even automates the dereferencing.
Thanks a lot dutch I will for sure look at iterator stuff, haven't really dug into it at all so far and it seems like a handy thing to be able to use.

Cheers!
It's actually a really simple idea. Here's a simplistic (and only partially implemented) example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>

class IntArray {
    int  m_size;
    int *m_array;

    class iterator {
        int *ptr;
    public:
        iterator(int *p) : ptr(p) {}
        int operator*() const { return *ptr; }
        iterator& operator++() { ++ptr; return *this; }
        bool operator!=(iterator it) const { return it.ptr != ptr; }
    };

public:
    IntArray(int sz) : m_size(sz), m_array(new int[sz]) {
        for (int i = 0; i < sz; ++i) m_array[i] = 0;
    }
    ~IntArray() { delete[] m_array; }
    iterator begin() const { return iterator(m_array); }
    iterator end() const { return iterator(m_array + m_size); }
};

int main() {
    IntArray a(10);

    // manual loop
    for (auto it = a.begin(); it != a.end(); ++it)
        std::cout << *it << ' ';
    std::cout << '\n';

    // automatic loop
    for (int n: a) std::cout << n << ' ';
    std::cout << '\n';
}

Messy, not recommended, but there you go. It might be the way you were thinking.
( 'auto' sometimes means trouble-by-obscurity )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <string>
#include <vector>


int main()
{
    std::vector<std::string> str = { "one", "two", "three" };
    std::string number;
    
    std::cout << " Type in a number between 0 and 10 : ";
    std::cin >> number;
    
    bool number_matches = false;
    
    int count = 0; // <--
    
    for (auto i = str.begin(); i != str.end(); ++i)
    {
        if (number == str[count]) //<--
        {
            number_matches = true;
            break;
        }
        count++; // <--
    }
    
    std::cout << "\n Is your number, one, two or three? : ";
    
    if (number_matches) std::cout << "Yes";
    else std::cout << "No";
}


Type in a number between 0 and 10 : two

 Is your number, one, two or three? : Yes 
Last edited on
Thank you for taking the time to post this, it indeed makes things a lot clearer for me!

I'm riddled by one last thing though. Using an automatic loop, is there a way to get the number of the nth element that is being accessed? Let me illustrate to make it more concrete.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <string>
#include <vector>

int main()
{
    std::vector<std::string> str = { "zero", "one", "two" };
    int string_number = 999;
    std::string input;
    
    std::cout << " Type out a number bewteen 0 and 2, all in letters   _";
    std::cin >> input;
    
    for (const auto& i : str)
    {
        if (input == i)
        {
            //I want here to assign the number of the element accessed in the vector to "number"
        }
    }
    
    std::cout << "\n Number: " << string_number;
    
}


in other words what I would like to do is the equivalent of:
1
2
3
4
5
6
7
    for (int i = 0 ; i < str.size(); ++i)
    {
        if (str[i] == input)
        {
            string_number = i;
        }
    }
Last edited on
Thanks againtry, but I would like to avoid the use of a counter if there is already iterators in my code and vice versa. But I see what you did there, it's quite clever and I'll certainly keep it in mind if I ever need to do something like this :)
Yeah HOOGO it wasn't meant as any sort of adequate alternative but just to highlight perhaps the 'old way thinking' and the need to treat iterators in the 'special' way dutch has shown.

There is nothing wrong with auto but the general theme from experts (IIRC core guideines etc, is be aware of what it means because auto obscures the type we are accustomed to)

It's not my call but FWIW feel free never to use what I wrote - I wouldn't :)
Using an automatic loop, is there a way to get the number of the nth element that is being accessed?

There is no way to do that with the automatic loop (which I think is actually called a "range-based for loop").
Even if you wrote your own iterator with a get_index method it still wouldn't work with the range-based loop since you have no access to the underlying iterator.
The usual way to have access to the index is to use a separate index variable. Of course, at that point you may as well use an old-style index-based loop. But it's somewhat a matter of taste.

1
2
3
4
5
6
7
8
9
    unsigned i = 0;
    for (const auto& s: str)  {
        if (s == input) break;
        ++i;
    }
    if (i < str.size())
        std::cout << "Found at index " << i << '\n';
    else
        std::cout << "Not found.\n";
Well, CRAP! Internet connection glitch resulted in a double-post.

My fault, mea culpa.
Last edited on
If using Visual Studio 2017/2019, hovering the mouse pointer over a variable being declared using auto pops up a tooltip that reveals the underlying data type being used.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>

int main()
{
   std::vector<int> vec { 1, 2, 3, 4, 5 };

   // access the elements by iterator
   for (auto itr { vec.begin() }; itr != vec.end(); itr++)
   {
      // auto is using std::vector<int>::iterator
   }

   // access by value (the vector is essentially copied)
   for (auto itr : vec)
   {
      // auto is using int (the data type of the contained values?)
   }
}

I discovered a few minutes ago this intellisense help about VS and auto. I thought I'd share it so auto can become less mysteriously obscuring.

I am NOT suggesting to use (over-use) auto for more than a few select uses, such as range-based for loops and declaring iterators.
Last edited on
With a suitable library, you could write something like

1
2
3
4
for (auto&& [i, elt] : zip(iota, vec))
{
  // elt is the element, i is the index
}

Last edited on
@mbozzi, kinda "funny" you should mention this, I was looking at the cppreference page on range-based for loops and noticed the && use of forward referencing.

https://en.cppreference.com/w/cpp/language/range-for

Or is your example using structured binding?

Is zip() in Boost or a custom function you wrote?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

int main()
{
    std::vector<std::string> str = { "zero", "one", "two" };
    int string_number;
    std::string input;
    
    std::cout << " Type out a number between 0 and 2, all in letters   _";
    std::cin >> input;
    
    string_number = std::find( str.begin(), str.end(), input ) - str.begin();
    if ( string_number == str.size() ) string_number = -1; // avoid panic
    
    std::cout << "\n Number: " << string_number;
}
I was looking at the cppreference page on range-based for loops and noticed the && use of forward referencing. Or is your example using structured binding?

It's a structured binding declaration: auto&& is used to declare the uniquely-named hidden variable whose subobjects are bound by the structured bindings elt and i.

Structured bindings are not a new concept. Common Lisp has long included a standard-library macro named DESTRUCTURING-BIND which has a similar, more general behavior:
http://www.gigamonkeys.com/book/beyond-lists-other-uses-for-cons-cells.html#destructuring-bind
http://clhs.lisp.se/Body/m_destru.htm

Is zip() in Boost or a custom function you wrote?

Yes and yes. Implementations of zip exist in Boost.Range at least, as well as (eventually) the C++20 standard library.
Last edited on
I'm still trying in vain to squish C++17 into my oatmeal mush brain, structured binding being one of the things added to the standard.

I did a 'net search for zip, as well at cppreference, and nothing seems to have been found. Other new C++20 features show up.

Are you using a specialized Boost iterator, the zip iterator? Looks interesting now that I found some documentation.
I believe the standard one will be called std::views::zip or something like that:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1035r3.html#zip_view

The Boost one is called boost::range::combine (closely related to the zip_iterator you found. Sorry, thought it was called zip):
https://www.boost.org/doc/libs/1_72_0/libs/range/doc/html/range/reference/utilities/combine.html

The version in range-v3 is called zip_view:
https://ericniebler.github.io/range-v3/structranges_1_1zip__view.html

Overall, there are lots of implementations about that have slightly different behavior (and different names).
@Duthomhas implemented one recently; his was named operator,.

Also, happy new year!
Last edited on
Furry Guy, mbozzi, lastchance, dutch and againtry : thank you so much for the further information on the subject. This thread has helped me a lot
Pages: 12