Range based for loop for heap allocated arrays

Viewed 1094

Consider the following code which will cout success exactly three times:

int arr [3];

for(int& value : arr )
    std::cout << "success" << std::endl;

If I try to allocate array on the heap, there are problem. This code doesn't compile:

int* ptr = new int[3];

for(int& value : *ptr )
    std::cout << "success" << std::endl;

Since the pointer was dereferenced, the types should be the same. So I have some questions:

  • what is the fundamental difference between when I ask from the hardware in the two expressions. I'd like to understand why the latter don't make sense.
  • Can I make it work with a small change?
2 Answers

Since the pointer was dereferenced, the types should be the same.

Take a closer look at the type of the pointer: int* ptr. It is a pointer to int Compare that to type of arr which is int[3]. Those types are different. So, your assumption that the types should be same is wrong. int[3] is a range, while int is not.

The array-new-expression returns a pointer to first element of the array. That is what ptr points to. The memory address of the first element is the same as the whole array, but the type of the variable determines how it can be used.

Can I make it work with a small change?

Since the value of the pointer to the first element is the same as a pointer to the whole array, you can reinterpret the pointer as the other type using a cast:

auto arr_ptr = std::launder(reinterpret_cast<int (*)[3]>(ptr));
for(int& value : *arr_ptr)

That said, typically dynamic arrays are used because they allow the size to be determined at runtime. The size in that cast must be known at compile time.

Furthermore, your example code leaks the array. While the leak is easy to fix in this code, manual memory management is in general difficult, and unnecessary. You're most certainly better off using std::vector when you need a dynamic array:

std::vector<int> v(3);
for(int& value : v)

A raw array only supports the range-based for syntax if the visible declaration includes the number of elements, i.e. int arr[3] in the first case and int* prt in the second case. In the first case this is given (but still you should prefer std::array if possible), but not in the second case you allocate memory on the heap and the information about the size is gone. You can circumvent this, if you simply use std::array rather than the raw array.

Since the pointer was dereferenced, the types should be the same.

Taking a closer look under the hood, the reason for this is, that in the first case you have an array and in the second case you have a pointer, which is NOT even the same type.

This misinterpretation of pointer and array equaliuty keeps to be broadcasted over C++ tutorials, but this is wrong. This might be due to the fact, that when passing an array to a function taking a pointer of that type, the array decays to a pointer, known as array-decay.

Can I make it work with a small change

Yes you can. Use std::array or st::vector which will make it look like that for std::array:

#include <iostream>
#include <array>

int main()
{
    std::array<int, 3>* ptr = new std::array<int, 3>;

    for(int& value : *ptr )
        std::cout << "success" << std::endl;
}

For brevity I did not include the delete for the pointer which you should always do.

However, if you allocate memory on the heap, it is almost always best to use std::vector as deallocation will be taken care for automatically. The program would than read:

#include <iostream>
#include <vecor>

int main()
{
    std::vector<int> vec(3);

    for(int& value : vec)
        std::cout << "success" << std::endl;
}
Related