Introduction to std::array

While std::vector excels at runtime-sized collections, its limited constexpr support restricts compile-time optimizations. When you need arrays evaluated at compile time, std::array is the solution.

When fixed-size arrays make sense

Dynamic arrays like std::vector provide flexibility but involve trade-offs:

  • Slight performance overhead compared to fixed-size arrays
  • Minimal constexpr support (limited to trivial uses)

Modern C++ emphasizes constexpr for safer, more optimizable code. When your array size is known at compile time and you want compile-time evaluation, std::array delivers.

Best Practice
Use `std::array` for constexpr arrays with known sizes. Use `std::vector` for runtime-sized or non-constexpr arrays.

Declaring std::array

std::array resides in the <array> header. Unlike std::vector, it requires the size as a template argument:

#include <array>
#include <vector>

int main()
{
    std::array<int, 4> slots{};     // std::array of 4 ints
    std::vector<int> items(4);       // std::vector of 4 ints (comparison)

    return 0;
}

std::array requires two template arguments:

  1. Element type (int)
  2. Array length (4) - a non-type template parameter

The length must be a constant expression

Unlike std::vector which accepts runtime sizes, std::array demands a constant expression for length:

#include <array>

int main()
{
    std::array<int, 6> a{};                 // Literal constant

    constexpr int capacity{ 10 };
    std::array<int, capacity> b{};          // Constexpr variable

    enum Weapons { sword, bow, staff, max_weapons };
    std::array<int, max_weapons> c{};       // Enumerator

    return 0;
}

Non-const or runtime values won't compile:

#include <array>

int main()
{
    int slots{ 8 };
    std::array<int, slots> d{};  // Error: slots is not constexpr

    return 0;
}
Warning
`std::array` permits zero length, creating an object with no data. Accessing elements of such arrays causes undefined behavior. Use `empty()` to check for this condition.

Aggregate initialization

std::array is an aggregate (it lacks constructors), so it uses aggregate initialization:

#include <array>

int main()
{
    std::array<int, 4> levels{ 1, 5, 12, 25 };     // List initialization (preferred)
    std::array<int, 3> ranks = { 100, 250, 500 };  // Copy-list initialization

    return 0;
}

Elements initialize sequentially from index 0.

Without initializers, elements are default-initialized (often leaving them uninitialized for fundamental types). Always use empty braces for value-initialization:

#include <array>

int main()
{
    std::array<int, 4> uninitialized;    // Elements may contain garbage
    std::array<int, 4> zeroed{};         // All elements are zero (preferred)

    return 0;
}

Too many initializers cause compilation errors; too few value-initialize remaining elements:

#include <array>

int main()
{
    std::array<int, 2> a{ 10, 20, 30 };  // Error: too many initializers
    std::array<int, 5> b{ 10, 20 };      // OK: b[2], b[3], b[4] are zero

    return 0;
}

Const and constexpr std::array

std::array can be const:

#include <array>

int main()
{
    const std::array<int, 3> thresholds{ 100, 500, 1000 };
    // Elements are implicitly const

    return 0;
}

More importantly, std::array supports constexpr fully:

#include <array>

int main()
{
    constexpr std::array<int, 5> fibonacci{ 1, 1, 2, 3, 5 };
    // Evaluated at compile time!

    return 0;
}

This constexpr capability is std::array's defining advantage.

Best Practice
Define `std::array` as constexpr whenever possible. If constexpr isn't needed, consider `std::vector` for its flexibility.

Class template argument deduction (C++17)

CTAD allows the compiler to deduce both type and length:

#include <array>

int main()
{
    constexpr std::array scores{ 85, 92, 78 };        // Deduces std::array<int, 3>
    constexpr std::array weights{ 1.5, 2.3, 0.8 };    // Deduces std::array<double, 3>

    return 0;
}
Best Practice
Use CTAD to let the compiler deduce `std::array` type and length from initializers.

CTAD doesn't support partial specification (as of C++23):

#include <array>

int main()
{
    std::array<int> a{ 1, 2, 3 };  // Error: length missing
    std::array<3> b{ 1, 2, 3 };    // Error: type missing

    return 0;
}

Deducing length with std::to_array (C++20)

std::to_array enables length deduction when you need to specify only the type:

#include <array>

int main()
{
    constexpr auto arr1{ std::to_array<int, 4>({ 5, 10, 15, 20 }) };  // Both specified
    constexpr auto arr2{ std::to_array<int>({ 5, 10, 15, 20 }) };     // Type only
    constexpr auto arr3{ std::to_array({ 5, 10, 15, 20 }) };          // Deduce both

    return 0;
}

However, std::to_array creates a temporary then copies it, making it more expensive than direct construction. Use it only when CTAD can't determine the type:

#include <array>

int main()
{
    // No literal for short, so specify the type explicitly
    constexpr auto levels{ std::to_array<short>({ 1, 2, 3, 4 }) };

    return 0;
}

Accessing elements

Use operator[] just like std::vector:

#include <array>
#include <iostream>

int main()
{
    constexpr std::array<int, 5> ranks{ 10, 25, 50, 100, 200 };

    std::cout << ranks[2] << '\n';  // Prints 50
    std::cout << ranks[8] << '\n';  // Undefined behavior: out of bounds

    return 0;
}

As with other containers, operator[] performs no bounds checking. Invalid indices cause undefined behavior.

Summary

std::array vs std::vector: std::array is a fixed-size array with complete constexpr support, ideal for compile-time arrays. std::vector is dynamic but has minimal constexpr support, making it unsuitable for compile-time operations.

Non-type template parameters: std::array requires two template arguments: element type and array length. The length is a non-type template parameter of type std::size_t that must be a constant expression.

Aggregate initialization: std::array is an aggregate (no constructors), using aggregate initialization. Elements initialize sequentially from index 0. Without initializers, elements are default-initialized; use empty braces for value-initialization.

Constexpr capabilities: std::array has complete constexpr support, enabling compile-time array creation and manipulation. This is its primary advantage over std::vector.

CTAD support (C++17+): Class Template Argument Deduction lets the compiler deduce both element type and array length from initializers, eliminating explicit template arguments.

std::to_array (C++20): Allows length deduction when specifying only the element type. However, it creates and copies a temporary, making it costlier than direct construction. Use only when CTAD can't determine the type.

Zero-length arrays: std::array permits zero length, creating an object with no data. Accessing elements causes undefined behavior. Use empty() to check for this condition.

Choose std::array for constexpr arrays with known, fixed sizes. Otherwise, prefer std::vector for flexibility and move semantics.