Reputation: 589
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
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
Reputation: 56
To turn it into range use
boost::make_iterator_range(pCollection, pCollection+3)
Upvotes: 1
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
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