Arrays decay when passed to functions in C++: an array argument is converted into a pointer to its first element, and the length is lost in the process. This one rule explains why a function cannot tell how big an array is, why it cannot create a larger array, and why passing an array by reference needs a slightly different syntax.
What decay means in practice
A function parameter declared as an array is really a pointer parameter. The compiler rewrites the signature for you.
#include
void print_ratings(const int ratings[], int count) {
for (int i = 0; i < count; ++i) {
std::cout << "book " << i + 1 << ": " << ratings[i] << '\n';
}
}
int main() {
int ratings[3] = {4, 5, 3};
print_ratings(ratings, 3);
return 0;
}The declaration const int ratings[] is read by the compiler as const int* ratings. The 3 in the definition is documentation, not information the compiler keeps. The function genuinely does not know how many elements there are, which is why count has to be a separate parameter.
The output shows the three ratings printed in order:
book 1: 4
book 2: 5
book 3: 3Why the length has to travel separately
Because the array decays, the function receives only a pointer. Two arrays of different sizes decay to the same type, so the compiler cannot check anything.
#include
void print_all(const int values[]) {
int index = 0;
// There is no way to know when to stop: values[index] is
// valid only while index is inside the array.
while (index < 4) {
std::cout << values[index] << ' ';
++index;
}
std::cout << '\n';
}
int main() {
int ratings[3] = {4, 5, 3};
print_all(ratings);
return 0;
}This program reads past the end of a three-element array. It might print the right thing by luck, or it might crash, or it might print nonsense. The compiler cannot warn you, because from its point of view the function was handed a const int* and the number 4 came from nowhere.
Two habits prevent this class of bug. Always pass the count alongside the pointer, and prefer std::array or std::vector when the size is genuinely part of the data.
Passing by reference to avoid the copy
An array parameter is already a pointer, so the array is never copied. But you can make the reference explicit, which documents the intent and protects against the pointer being reseated.
#include
void sort_front(int (&values)[4]) {
for (int i = 1; i < 4; ++i) {
int current = values[i];
int j = i - 1;
while (j >= 0 && values[j] > current) {
values[j + 1] = values[j];
--j;
}
values[j + 1] = current;
}
}
int main() {
int ratings[4] = {5, 3, 4, 2};
sort_front(ratings);
for (int r : ratings) {
std::cout << r << ' ';
}
std::cout << '\n';
return 0;
}The syntax int (&values)[4] means "a reference to an array of four ints". Because the size is part of the type, the compiler now checks that the caller really passes a four-element array.
The output shows the ratings sorted:
2 3 4 5std::array keeps the size
std::array is a class, so it does not decay when passed to a function. Its size travels with it and the compiler verifies it.
#include
#include
void print_all(const std::array& ratings) {
for (std::size_t i = 0; i < ratings.size(); ++i) {
std::cout << "book " << i + 1 << ": " << ratings[i] << '\n';
}
}
int main() {
std::array ratings = {4, 5, 3};
print_all(ratings);
return 0;
}The size is now part of the signature, the loop condition uses .size(), and the compiler rejects a call with an array of the wrong length. No manual count parameter is needed.
Pointers into the middle of an array
Because an array decays to a pointer, you can pass a pointer to any element, and the function will treat that element as the start of a sequence.
#include
double average(const double* begin, const double* end) {
if (begin == end) {
return 0.0;
}
double sum = 0.0;
const double* cursor = begin;
while (cursor != end) {
sum += *cursor;
++cursor;
}
return sum / static_cast(end - begin);
}
int main() {
double prices[4] = {4.00, 6.00, 8.00, 10.00};
double mid = average(prices, prices + 2);
std::cout << "average of first two: " << mid << '\n';
return 0;
}The begin-and-end pointer pair is the shape the standard library uses for every range, and it is how you pass a *portion* of an array to a function rather than the whole thing.
Quick reference
- An array argument decays to a pointer to its first element; the size is lost.
- Always pass the element count as a separate parameter, or use
std::array. int (&values)[4] is a reference to a four-element array, and the size is checked.std::arraydoes not decay, so its size travels with it.array + nis a pointer to the nth element, andarray + ntoarray + mdefines a range.- Never write a loop over an array parameter without a length — the compiler cannot help you.
One more habit: whenever you write a function that takes a pointer and a count, ask yourself whether a [[std::span]] would express the same thing more safely. In C++20 it would, and the length would no longer be a separate argument that can disagree with the pointer.
Try it yourself: write a function that finds the highest rating in an array, first taking a raw array and a count, then taking a [[std::array]]. Delete the count parameter from the raw-array version and see what the compiler says. Finally, pass only the middle two elements of a five-element array by passing [[data + 1]] and [[data + 3]] and confirm the function sees exactly those two.