简体   繁体   中英

stl::iterators with raw pointers

I want to use iterators with C++ arrays, but with raw pointers too. I can do with a static vector:

#define SIZE 10
int vect[SIZE] = {0};
vect[3] = 5;
int* p = std::find(std::begin(vect), std::end(vect), 5);
bool success = p != std::end(vect);

How can be possible to do it with a raw pointer (maybe a heap allocated vector)? Of course the compiler does not know the size of the data, so this code

int* pStart = vect;
std::find(std::begin(pStart), std::end(pStart), 5);

gives

error C2784: '_Ty *std::begin(_Ty (&)[_Size])' : 
could not deduce template argument for '_Ty (&)[_Size]' from 'int *'

Is it possible to make begin() and end() aware of it?

Is it possible to make begin() and end() aware of it?

It's possible to implement std::begin for a pointer, but it is impossible to implement std::end (because as you say, the size is unknown), so it is a bit pointless .

However, you don't need either of those to use std::find :

int* p = std::find(pStart, pStart + SIZE, 5);

No it is not possible to use std::begin and std::end on a pointer. Unlike an array whose size is part of the type and therefor deducible a pointer does not hold the size of the thing it points to. In your case with a pointer you would have to use

std::find(pStart, pStart + SIZE, 5);

The way to avoid this though is to use std::vector when you are not going to know what the szie will be at compile time. It will manage the memory for you and provides begin and end member functions.

Here:

std::begin(pStart), std::end(pStart)

you're trying to take the beginning and end of a pointer. Nope!

Instead, you meant:

std::begin(vect), std::end(vect)

This is the same whether you use an array, or a std::array , or a std::vector , or a particularly large elephant — to get the bounds of a container, you need the container.

I want to use iterators with C++ arrays, but with raw pointers too.

You got it backwards. Raw pointers are iterators. They iterate over arrays.

You can just use them for all the things you would otherwise accomplish with std::begin and std::end . Most notably, you can pass them to C++ standard algorithms in <algorithm> .

A pointer itself cannot be iterated. Iterators cannot be iterated.

 int* pStart = vect; std::find(std::begin(pStart), std::end(pStart), 5); 

Very loosely speaking, a pointer is just a number. What is the "begin" and "end" of a number? This piece of code makes as little sense as the following:

int i = 123;
std::find(std::begin(i), std::end(i), 5); // error

Is it possible to make begin() and end() aware of it?

A pointer may point to the beginning of some array. But that knowledge must be kept along with the pointer. You need to maintain a size or an end pointer along with a start pointer, and keep all that data together.

That's exactly what std::array and std::vector do for you.

When I deal with naked arrays I rely on simple containers to make them compatible with C++'s general treatment of ranges .

For example:

#include <iostream>
#include <memory>    // for std::unique_ptr
#include <algorithm> // for std::reverse
#include <numeric>   // for std::iota

template<typename T>

class range_view {

 public:

  range_view(T* data, std::size_t size)
      : m_data { data },
        m_size { size } { }

  const T* begin() const { return m_data; }
  const T* end() const { return m_data + m_size; }

  T* begin() { return m_data; }
  T* end() { return m_data + m_size; }

 private:

  T* m_data;
  std::size_t m_size;
};

int main() {

  // this is your actual data
  auto pdata = std::make_unique<int>(20);

  // this is a handle you use to work with your data
  auto data_view = range_view<int>(pdata.get(), 20);

  // for example, you can fill it with numbers 0, ... , N - 1
  std::iota(data_view.begin(), data_view.end(), 0);

  // it is now compatible with c++'s range-based operators
  std::cout << "my data...\n";
  for(auto && item : data_view) std::cout << item << " ";

  std::reverse(data_view.begin(), data_view.end());
  std::cout << "\nreversed...\n";
  for(auto && item : data_view) std::cout << item << " ";
  std::cout << "\n";
}

Compile and run

$ g++ example.cpp -std=c++14
$ ./a.out
my data...
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
reversed...
19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 

You still have to worry about passing the correct dimension to the constructor, and it will fail miserably if the underlying pointer is deleted, but you had to worry about that anyway.

While others answers already explains why you should revisit your design I want to state one option, that was not described earlier.

Your compiler diagnosis message almost clearly states that the compiler falls to deduce arguments and that arguments looks exactly like reference to С-style array.

Reference to C-style array doesn't decay to pointer, so you can pass reference to C-style array to std::begin and std::end in that particular case as an option:

#include <iostream>
#include <algorithm>

constexpr size_t SIZE = 10;

int main() {
    int vect[SIZE] = {0};
    vect[3] = 5;

    int (&ref_to_vect)[SIZE] = vect;    // declaring reference to C-array

        // and using reference to C-array which doesn't decay to pointer opposite to passing array "by value" semantics
    int* p = std::find(std::begin(ref_to_vect), std::end(ref_to_vect), 5);
    bool success = p != std::end(vect);
    std::cout << (success ? "Found 5" : "5 not found :(");

    return 0;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM