1

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;
}
2
  • 4
    a vector is a better choice for this type of use. Commented May 11, 2017 at 20:28
  • Please explain how you think this could work. Commented May 11, 2017 at 20:29

4 Answers 4

7

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.

Sign up to request clarification or add additional context in comments.

2 Comments

@AppWriter No. Technically, an array can decay into a pointer. But an array is a distinct type from the pointer it can decay into
@AppWriter: No, an array is an array. In most contexts, an expression of array type is implicitly converted to a pointer to its first element, but that doesn't make arrays pointers.
4

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.

1 Comment

So I guess the smart pointers have the same issue. Now I understand better the benefits of a vector that contains the size information. Thanks a lot!
2
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.

1 Comment

The span was exactly what I was looking for. Thanks a lot!
1

To turn it into range use

boost::make_iterator_range(pCollection, pCollection+3)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.