Reference to Array

C-style Array Variables, Parameters and References

This article discusses pointer-to-array and reference-to-array types in C++. In particular, we pay attention to at least one useful application of this in C++11 and newer, which will reduce de­pen­den­ce on the C preprocessor.
PREREQUISITES — You should already…
  • have been writing and compiling C++ programs for a while.

Introduction

For many C and C++ programmers, the mention of simple pointers is often enough to cause un­ease. This is even worse when the subject of pointer-to-array types comes up. Fortunately, poin­ter-to-array types are not common, nor very useful. In C++, reference types were added, lead­ing to the cor­res­pon­ding: reference-to-array types.

Reference Fundamentals

A reference type is not a type in any traditional sense; it is basically the name for a syntax that tells the compiler to implicitly:

A reference type, say T&, has some relationship with a corresponding pointer type: T*. Consider the following code extract, using an int pointer (int* P;) and an int reference (int& R;) to refer to an int variable (int I;):

The memory layout involving P, and all statements involving P, will be exactly the same for R. The only difference between them, is that the compiler automatically initialises R with an address, and automatically dereferences R when it is used in an expression.

Reference Parameters

Parameters are simply variables of the functions called, which are allowed to be in­i­ti­alis­ed by cal­lers. They have the same lifetime, namespace and scope as other local variables. If a variable can be of type int&, so can a parameter.

The call to f2() implicitly initialises the parameter with the address of i, while we must do it ex­pli­citly when calling f1(). The reference to r inside f2() implicitly applies the in­di­rec­tion op­e­ra­tor, whereas we have to do it explicitly with p in f1().

Arrays As Parameters

In C++, it is possible to define functions that take parameters of type “reference to array”. This does not refer to the dubious syntax where a pointer parameter may be written as if it is an ar­ray:

The above function header is 100% equivalent to:

And so is the following function header, where the size X is given for the “array”:

The X can be any constant integer type value, and arr will still be treated as type int*. In other words, you can pass it an array of any size. This is a syntactic hangover from C, inherited by C++. It is one more reason not to use C-style arrays in C++; you should rather use std::array.

A pointer-to-array type looks like this: T(*)[N], so a reference-to-array type has a similar syntax: T(&)[N]. Here they are both in use:

Reference and pointer types are most often used as parameters. Instead of local variables like rar and par above, their types could be applied to parameters, as in the following code extract (referring to the same arr above):

If the size of arr should change in its definition, the above function calls will not be valid anymore. Here is a complete example:

arrayparams.cppArray & Pointer Reference

The output of all the functions will be exactly the same, and each will change another element of the original array (arr in main). This can be proved by examining the output of the first for() loop, which will be: 111 222 333 444 555.

While you can pass any int array to f0(), f1() and f2(), this is not true for f3() and f4(). The latter two can only take parameters obtained from expressions referring to arrays of 5 ints exactly (int[5]).

NOTEOverloading

As a consequence of the above, you cannot overload f0(), f1(), f2() and f4(), because the compiler would not be able to determine the correct type from the argument expressions at the point of call.

C-style Array Size Template Function

Although reference-to-arrays are not very common because of the fixed array size limitation, we can use them with C++11 templates to create a constant expression template function that will return the size of any array:

Because of the constexpr keyword, the function is evaluated at compile time. It can thus be used anywhere a constant expression is required, even if it may look unusual:

The ArrSize template function supersedes this common preprocessor macro:

This is another illustration of the power of C++ templates. Note that the above example depends on C++11 upwards (particularly: constexpr and noexcept, which are new keywords).

Notes

From C++17 on, you can use the std::size() function from the <iterator> header instead of the ArrSize() above. It works with any container having a size() member function, and with C-style arrays. Prior to C++17, you could have used the std::extent() function from the <type_traits> header (it works with a type though):

Another alternative, would be the std::distance() function. It re­quir­es ite­ra­tors, but can be used with C-style arrays. It can be generalised more with the std::begin() func­tion (or cbegin()), and the match­ing std::end() function (or cend()). These are all from the <iterator> header.


2017-11-18: Update to new admonitions. [brx]
2017-10-07: Added C++17 std::size(), and additional notes. Corrected a comment. [brx]
2016-11-19: Created. [brx]