std::array length and indexing

Like std::vector, std::array uses unsigned types for lengths and indices, presenting similar challenges. However, std::array's constexpr nature provides additional compile-time options.

The length has type std::size_t

std::array is declared as:

template<typename T, std::size_t N>
struct array;

The length non-type template parameter N is std::size_t (unsigned). When defining an array, the length must be std::size_t or convertible to it.

Since the length must be constexpr, signed-to-unsigned conversion isn't narrowing—the compiler verifies safety at compile-time.

std::array uses size_type, which is always std::size_t

Like std::vector, std::array defines a nested typedef size_type for lengths and indices. Unlike std::vector, std::array's size_type is ALWAYS std::size_t (no customization possible).

The template parameter uses std::size_t explicitly because size_type isn't defined yet at that point. Everywhere else uses size_type.

Getting array length

Three common approaches:

Option 1: size() member function

#include <array>
#include <iostream>

int main()
{
    constexpr std::array stats{ 25, 30, 18, 22 };
    std::cout << "Elements: " << stats.size() << '\n';

    return 0;
}

Returns unsigned size_type.

Option 2: std::size() non-member function (C++17)

#include <array>
#include <iostream>

int main()
{
    constexpr std::array stats{ 25, 30, 18, 22 };
    std::cout << "Elements: " << std::size(stats) << '\n';

    return 0;
}

Calls the size() member function, returning unsigned size_type.

Option 3: std::ssize() non-member function (C++20)

#include <array>
#include <iostream>

int main()
{
    constexpr std::array stats{ 25, 30, 18, 22 };
    std::cout << "Elements: " << std::ssize(stats) << '\n';

    return 0;
}

Returns a large signed integral type (typically std::ptrdiff_t).

Constexpr length from all three methods

Because std::array length is constexpr, all three functions return constexpr values even for non-constexpr arrays:

#include <array>
#include <iostream>

int main()
{
    std::array bonuses{ 50, 75, 100, 150 };  // Not constexpr

    constexpr int count{ std::size(bonuses) };  // OK: result is constexpr

    std::cout << "Count: " << count << '\n';

    return 0;
}

The conversion from constexpr std::size_t to int isn't narrowing.

Warning
This doesn't work for array parameters passed by reference due to a language defect (addressed in C++23):
#include <array>

void show(const std::array<int, 5>& arr)
{
    constexpr int count{ std::size(arr) };  // Error pre-C++23!
}

Workaround: use a function template with a length non-type parameter.

Subscripting with operator[] or at()

Both operator[] (no bounds checking) and at() (runtime bounds checking) expect size_type indices:

#include <array>
#include <iostream>

int main()
{
    constexpr std::array rewards{ 10, 25, 50, 100 };

    std::cout << rewards[2] << '\n';     // No bounds checking
    std::cout << rewards.at(2) << '\n';  // Runtime bounds checking

    return 0;
}

With constexpr indices, conversion to std::size_t isn't narrowing. With non-constexpr signed indices, you'll get narrowing warnings (same issue as std::vector).

Compile-time bounds checking with std::get()

For constexpr indices, use std::get() for compile-time bounds checking:

#include <array>
#include <iostream>

int main()
{
    constexpr std::array rewards{ 10, 25, 50, 100 };

    std::cout << std::get<2>(rewards) << '\n';  // OK: valid index
    std::cout << std::get<7>(rewards) << '\n';  // Compile error: index out of range

    return 0;
}

std::get() uses a non-type template argument for the index and static_asserts that it's within bounds. Since template arguments must be constexpr, this only works with constexpr indices.

Summary

Length type: The std::array length template parameter N has type std::size_t (unsigned). Since the length must be constexpr, signed-to-unsigned conversion isn't narrowing because the compiler verifies safety at compile-time.

size_type is always std::size_t: Unlike std::vector which can customize size_type, std::array's size_type is always std::size_t. The template parameter uses std::size_t explicitly because size_type isn't defined yet at that point.

Three ways to get length: The size() member function, std::size() non-member function (C++17), and std::ssize() non-member function (C++20). All work with std::array, with std::ssize() returning a signed type.

Constexpr length: All three length functions return constexpr values for std::array, even when the array itself isn't constexpr. This allows using the length in constant expressions and static assertions.

Subscripting: Both operator[] (no bounds checking) and at() (runtime bounds checking) expect size_type indices. With constexpr indices, conversion to std::size_t isn't narrowing. With non-constexpr signed indices, you may get narrowing warnings.

Compile-time bounds checking: std::get<N>() provides compile-time bounds checking for constexpr indices. It uses a non-type template argument for the index and static_asserts validity, preventing out-of-bounds access at compile-time.

Language defect (pre-C++23): Getting the constexpr length of array parameters passed by reference doesn't work prior to C++23. Workaround: use a function template with a length non-type parameter.

The key insight is that std::array's constexpr length enables compile-time operations and safety checks that aren't possible with runtime-sized arrays like std::vector.