5

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?
Mikkel Rev
  • 863
  • 3
  • 12
  • 31
  • 3
    When you are using heap allocated array, the size information of the array will be lost. – Mohit Aug 27 '18 at 11:26
  • Re: "will cout `success`" -- `cout` is a noun, not a verb. – Pete Becker Aug 27 '18 at 11:33
  • 1
    If `range_expression` is a container, it has member `begin` and `end`, otherwise it's a built-in array with call `std::begin(c)`. https://en.cppreference.com/w/cpp/language/range-for – rsy56640 Aug 27 '18 at 11:37

2 Answers2

5

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)
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Are you sure you are not running in UB land with that reinterpret_cast, as you are not casting to char*? –  Aug 27 '18 at 11:48
  • 2
    I am always unsure with reinterpret cast. –  Aug 27 '18 at 11:55
  • @Pi I can't blame you for that :) I added laundering to the example code just in case. – eerorika Aug 27 '18 at 12:03
  • If the types are not similar, than std::launder won't help. The outcome as far as I know is still UB. –  Aug 27 '18 at 12:19
  • It basically breaks down to the question if the array and the pointer are similar. If not, it is UB. But I honestly do not know. –  Aug 27 '18 at 12:26
  • @Pi I checked the standard, and the conversion is definitely at least unspecified if not UB without laundering. With laundering, I'm not sure either, perhaps we need to post another question. [cppreference](https://en.cppreference.com/w/cpp/utility/launder) uses an equivalent as an example, with comment "OK". – eerorika Aug 27 '18 at 12:41
  • The other [question](https://stackoverflow.com/questions/52039886/stdlaunder-in-conjunction-with-reinterpret-cast) – eerorika Aug 27 '18 at 13:25
  • Esssntially the answer to the new question is: it is valid –  Aug 28 '18 at 19:49
4

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;
}