Less White
Less White

Reputation: 589

Range-based loop does not work with pointer of arrays

Why the Range-based for loop does not work with pointer of arrays?

auto pCollection = new int[3] { 0,1,2 };
// error C3312: no callable 'begin' function found for type 'int *'
for (auto value : pCollection)
{
    std::cout << value << std::endl;
}
delete[] pCollection;

but can be used on arrays:

int collection[3]{ 0,1,2 };
for (auto value : collection)
{
    std::cout << value << std::endl;
}

Upvotes: 1

Views: 745

Answers (4)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275270

auto pCollection = new int[3] { 0,1,2 };

this isn't an int[3]. It is an int* that points to a buffer of 3 int.

The type carries no information about its size.

int collection[3]{ 0,1,2 };

this is an int[3]. Its type says how big it is.

Here we create two different sized new'd arrays

auto pCollection = (rand()%2)?new int[3] { 0,1,2 }:new int[5]{1,2,3,4,5};

and store a pointer to one or the other. The type of pCollection doesn't know how big it is. There is no way in C++ to get at its size, and as destruction is trivial, it would be acceptable for the OS to give us enough room for 8 ints and say "whatever" about the extra space. So even accessing low level memory APIs may not tell us how large it is.

Witn an actual int[3],

for (auto value : collection) {
  std::cout << value << std::endl;
}

this statement can use the type of collection to know how many elements to visit. std::begin and std::end are overloaded to do the right thing, and for(:) loops are similarly specified to do the right thing.

With an int*, there is no type information about its length stored.

We can store it ourselves. And provide a type that knows it.

Here is a quick one:

template<class It, class R=void>
struct range_t {
  It b, e;
  It begin() const { return b; }
  It end() const { return e; }

  range_t(It s, It f):b(std::move(s)), e(std::move(f)) {}
  range_t() noexcept(noexcept(It{})) :range_t({},{}) {}
  range_t(range_t const&)=default;
  range_t(range_t &&)=default;
  range_t& operator=(range_t const&)=default;
  range_t& operator=(range_t &&)=default;
  ~range_t()=default;

  decltype(auto) front() const { return *begin(); }
  decltype(auto) back() const { return *std::prev(end()); }

  using own_type = std::conditional_t<
    std::is_same<R,void>::value,
    range_t,
    R
  >;
  own_type without_front( std::size_t N=1 ) const {
    return {std::next(begin(), N), end()};
  }
  own_type without_back( std::size_t N=1 ) const {
    return {begin(), std::prev(end(),N)};
  }
  std::size_t size() const {
    return std::distance( begin(), end() );
  }
  bool empty() const {
    return begin()==end();
  }
};
template<class T>
struct span_t:range_t<T*, span_t<T>> {
  span_t(T* s, T* f):range_t<T*>(s, f) {}
  span_t(T* s, std::size_t l):span_t(s, s+l) {}

  T& operator[](std::size_t n)const{ return begin()[n]; }

  span_t( range_t<T*> o ):range_t<T*>(o) {}
  span_t( span_t const& ) = default;
  span_t& operator=( span_t const& ) = default;
  span_t() noexcept(true) : span_t(nullptr, nullptr) {}
};
template<class T>
span_t<T> span(T* s, T* f){ return {s,f}; }
template<class T>
span_t<T> span(T* s, std::size_t length){ return {s,length}; }

so now we can do this:

auto pCollection = new int[3] { 0,1,2 };
for (auto value : span(pCollection,3)) {
  std::cout << value << std::endl;
}
delete[] pCollection;

and bob is your uncle.

Note GSL has a better more complete span<T> type.

Upvotes: 2

HPPS
HPPS

Reputation: 56

To turn it into range use

boost::make_iterator_range(pCollection, pCollection+3)

Upvotes: 1

Quentin
Quentin

Reputation: 63114

Suppose your dynamically-allocated array is returned from a function:

int *pCollection = getCollection();

How will you find the end of the array? Well, you can't -- that pointer only points to the first element, it does not bundle any size information. In fact, it could point to a single int allocated with new and you wouldn't know either. Pointers just aren't containers -- only pointers.

Upvotes: 4

Pete Becker
Pete Becker

Reputation: 76245

A pointer is not an array. There is no way to know, from the pointer alone, how many elements there may or may not be at the location that a pointer points to.

Upvotes: 7

Related Questions