← C++, End to End

C++, End to End

Fixed-Size Arrays: std::array and C-style Arrays

When the number of elements is known at compile time and never changes, you don't need std::vector's dynamic resizing machinery at all. C++ gives you two fixed-size options: the C-style array, inherited from C, and std::array, which wraps the same underlying storage in a proper class with a real interface. Prefer std::array.

std::array as the safer default

c++ · live, editable, runnableOpen in Compiler Explorer ↗

std::array<T, N> stores its elements the same way a C array does, on the stack (or inline inside whatever object contains it), with zero extra runtime overhead. What you get for free is a real size(), bounds-checked access via .at(), and compatibility with the rest of the standard library's iterator-based interfaces.

Array decay to pointer

A C-style array, when passed to a function or otherwise used in most expressions, silently decays into a pointer to its first element. The array's length is not part of that pointer. This is one of C++'s oldest and most persistent footguns, inherited straight from C.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

This is exactly why so much old C and C++ code passes a separate length parameter alongside every raw array parameter, void process(int* data, int length), there's no other way for the function to know how many elements it's looking at. std::array doesn't have this problem: passing one by reference keeps its real type, size() intact, and no separate length parameter needed.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

When a C-style array is still the right call

Almost never, but not literally never. A few real situations where you'll still reach for one:

  • Interop with C APIs or C libraries that expect a raw pointer and length, or a fixed-size buffer laid out a specific way (e.g. char buffer[256] for a system call that fills it).
  • Extremely low-level or embedded contexts where even std::array's (zero-cost, but still present) header dependency and abstraction isn't wanted, though this is rarer than people assume; the abstraction really does compile down to the same machine code.
  • String literals, which are themselves C-style const char[N] arrays under the hood, though you'll almost always immediately hand them to a std::string or std::string_view rather than manipulate the raw array.

Outside of those, a C array offers nothing std::array doesn't already give you for free, plus the decay footgun. Default to std::array for fixed-size, std::vector for anything that grows or whose size isn't known until runtime.

Try it yourself: fix a decayed-array bug

This function is supposed to compute the sum of a 5-element array, but it always gets the wrong answer for arrays other than size 5, silently, with no compiler error. Explain why, and fix it using std::array so the mistake becomes impossible to make.

c++
int sumFive(int arr[]) {
    int total = 0;
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i) { // BUG
        total += arr[i];
    }
    return total;
}

sizeof(arr) inside the function is sizeof(int*) (the decayed pointer), not the array's true byte size, so sizeof(arr) / sizeof(arr[0]) computes something like 8 / 4 = 2, not 5, regardless of the actual array passed in. The loop silently only sums the first 2 elements.

c++ · live, editable, runnableOpen in Compiler Explorer ↗