Sunday 30 June 2013

A pointer is an iterator is... not exactly a pointer

I'll start by stating the obvious - while the last change I made in the previous post built, it would blow up in your face if you executed it, because this

&*buffer.end()


is a not-so-good idea. Since end() gives you one-past-the-end, dereferencing it will crash immediately (if you're lucky), or will crash during the presentation your soon-to-be-ex-CEO is making to the press on the launch of your brand new service (if you're not). I should've mentioned it, but I forgot to because my goal was not to execute the code, just to get it to build.

Now then, the next step was making sure the types involved were what I was expecting. To that end, I used this:

auto a = &*buffer.begin();
auto b = buffer.begin();
std::cout << typeid(a).name() << std::endl;
std::cout << typeid(b).name() << std::endl;


VC gave me this:

unsigned __int64 *
class std::_Vector_iterator<class std::_Vector_val<
    struct std::_Simple_types<unsigned __int64> > >

So, uint64* on one end, and vector<uint64>::iterator on the other.

GCC gave me this:

Py
N9__gnu_cxx17__normal_iteratorIPySt6vectorIySaIyEEEE

Yep, mangled. No biggie, after going through c++filt -t, I got this:

unsigned long long*
__gnu_cxx::__normal_iterator<unsigned long long*, 
    std::vector<unsigned long long, std::allocator<unsigned long long> > >

And there we have it, ulong long* on one end, and vector<ulong long>::iterator on the other.

So, the types were as I was expecting, i.e., there was nothing fancy going on with the types themselves. Which means that my understanding of the dereferencing of iterators was not entirely correct; i.e., iterators may share some traits with pointers (namely, dereferecing), but they're not interchangeable, as far as argument deduction is concerned. Let's see.

#include <vector>
#include <iostream>
 
void print(int i)
{
    std::cout << "int: " << i << std::endl;
}
 

void print(int* i)
{
    std::cout << "int*: " << *i << std::endl;
}
 

void print(std::vector<int>::iterator i)
{
    std::cout << "vector<int>::iterator: " << *i << std::endl;
}
 
int main() {
    std::vector<int> v;
    v.push_back(1);

    // CALLS void print(int i)
    print(v[0]);

    auto i = v.begin();

    // CALLS void print(std::vector<int>::iterator i).
    // IF WE REMOVE THIS FUNCTION, WE GET THE ERROR: NO MATCHING FUNCTION
    print(i);

    // CALLS void print(int i)
    // IF WE HAVE ONLY void print(int* i), THE ERROR IS
    // INVALID CONVERSION FROM 'int' TO 'int*'
    print(*i);

    // THIS CALLS void print(int* i)
    print(&*i + 0);
}


In order to get a call to void print(int* i), we must first dereference the iterator (thus, getting an int), and then take its address (the + 0 is optional).

OK, so the solution for the original code is simple. We must change this

template <typename I>
std::pair<double, double> minmax_times(I first, I last, size_t iterations)
{
  typedef typename std::iterator_traits<I>::value_type T;
... 

  std::pair<I, I> m0, m1;


to this

template <typename I>
std::pair<double, double> minmax_times(I first, I last, size_t iterations)
{
  typedef typename std::iterator_traits<I>::value_type T;
... 

  std::pair<T*, T*> m0, m1;


This time, I've built and executed it. It works.

No comments:

Post a Comment