passing int (&array)[] to a function C++
Asked Answered
W

3

5

There are three ways to pass an array to a function

1.

void changeArray(int array[]) {
    array[0] = 1111;
}
void changeArrayByPointer(int * array) {
    array[0] = 1111;
}
void changeArray(int (&array)[]) {
    array[0] = 1111;
}

I understood that 1 and 2 are practically the same, passing a pointer to the first element.

But what about passing by reference int (&array)[]?

What is the difference? When would you use int (&array)[]?

Initially, I thought that that's the syntax to explicitly pass an array by reference, so the programmer knows that the data is not copied (as all other data types are passed by a reference without creating a copy).

Watkins answered 20/8 at 4:14 Comment(7)
" When would you use int (&array)[]?..." When you want to pass an array(by reference) without being able to iterate through it.Dorsad
Note also that int (&array)[] is only valid from c++20. So the question is basically, why c++20 allowed this usage.Dorsad
@john Yeah, we can remove it.Dorsad
@AmmarHoque No, int (&array)[] and int& array are not same in c++. See demo.Dorsad
Not sure why this question got a down vote. It's an interesting question about which I knew nothing. I'm looking forward to an answer.Corry
int array[] and int *array are, for function arguments, equivalent (they have different meanings in other contexts). Before C++20, int (&array)[] is invalid syntax (passing a reference to an array requires an array dimension, which is known at compile time).Headed
Who told you to use arrays like this? There are more ways to pass an array to the function, all better than using the "C" syntax you propose (which loses all size information and allows for all kind of out of bound memory access bugs). Use changeArray(std::span<int>) to modify (a part of an array), or changeArray(std::vector<int>&) or if you know the size of the array at compile time use changeArray(std::array<int,8>&).Haunch
D
6

When would you use int (&array)[]?

void changeArray(int (&array)[]) is more restrictive in that it only accepts arrays by reference. You can pass as argument(by reference) an array of any size with elements of type int. This also prevents one from passing a null pointer.

This was allowed through p0388r4 and cwg393 that mentions the rationale. They mention:

Motivation and impact

As of core issue 393,CWG393 function parameters can be pointers or references to arrays of unknown bound. However, binding such a parameter to an array of known bound isn't permitted:

void f(int(&)[]);
int arr[1];

f(arr);          // Error
int(&r)[] = arr; // Error

This restriction is unjustified and should be removed. One consequence of our approach is the fact that *

Dorsad answered 20/8 at 4:47 Comment(0)
F
6

The difference is that the third form is more restrictive, while the first and second forms could benefit from a null check.

The first (and second) form can accept an argument that is not an array, as long as the argument converts to an int pointer.

void changeArray(int array[]) {
    array[0] = 1111;
}

int main() {
    changeArray(nullptr); // Causes a segmentation fault, but does compile.
}

The third form cannot.

void changeArray(int (&array)[]) {
    array[0] = 1111;
}

int main() {
    // changeArray(nullptr);
    // error: invalid initialization of non-const reference of type 'int (&)[]'
    //        from an rvalue of type 'std::nullptr_t'
}

An argument that is an array (of int) would be accepted by any of these functions.


I understood that 1 and 2 are practically the same, passing a pointer to the first element.

Not just practically the same. They are the same. Presumably, that is why you named the second function changeArrayByPointer instead of changeArray, since otherwise you'd get a function redefinition error. (If you want to test this out, try putting the declaration void changeArray(int array[]); before your main function and the definition void changeArray(int *array) { array[0] = 1111; } after. The definition links to the declaration, despite looking different.) See also Difference between passing array, fixed-sized array and base address of array as a function parameter.

In contrast, the first and third forms are distinct signatures, so they can overload the name changeArray. (However, if both overloads are in scope, trying to call one with an array argument is ambiguous.)

Fairway answered 20/8 at 7:7 Comment(0)
D
5

(3) also passes a pointer internally (references are implemented as pointers on all platforms I know of).

But unlike (1) and (2), it only accepts arrays, not arbitrary pointers.

Like (1) and (2), it doesn't preserve the length information.


I'm not sure why would anybody want to use (3). It seems to be a strictly worse alternative to (1) and (2), because you no longer can pass subarrays, standard containers, etc.

Decillion answered 20/8 at 4:48 Comment(8)
SFINAE, probably?Guidry
"I'm not sure why would anybody want to use (3)." -- It prevents passing in a null pointer, so there is no need to check for null.Fairway
@Fairway "prevents passing in a null pointer" is a good point. Regarding having to check for null pointers, I believe this is a misconception. You don't have to check for null pointers either way, you can just crash. Some C standard library functions do this, there's nothing wrong with it.Decillion
@Decillion "You don't have to check for null pointers either way, you can just crash." -- True enough. It depends on what your requirements are.Fairway
The reason I bring is up is because I have coworkers who will use references instead of pointers in a parameters struct, "because then we don't have to check for null pointers". And then when I need to copy the struct with one pointer/ref modified, tell me to manually do elementwise copy using aggregate init. >:oDecillion
I wonder why would the ISO committee waist time on such impractical syntax? What's good about losing size information? How many cases are actually covered by the proposed optimization?Haller
@Haller No idea. One thing I know is that the type existed well before C++20 (extern int arr[];), C++20 just added some extra conversions.Decillion
@Decillion as you illustrated it's extern. In the era of modules that keyword should logically become obsolete too. That usage means size of the array is also defined by the evaluating TU. So there's a size caculation function or extern variable in that TU. There's no point in using such functions/arrays in automatic contexts..Haller

© 2022 - 2024 — McMap. All rights reserved.