A function that receives a raw pointer often also needs a length, and those two arguments can disagree. A span combines a pointer-like view with an extent, making the intended sequence easier to pass around. It still borrows storage. An array or vector remains responsible for owning the elements and keeping them valid.
This guide requires C++20 for std::span. Std::array has been available since C++11. You will pass fixed storage through a read-only span, take a bounded subview, and implement a checked two-dimensional grid using flat storage. C++23 mdspan is a separate multidimensional view facility; this lesson's explicit indexing does not depend on it.
Array owns fixed-size contiguous storage
Std::array holds a compile-time number of elements and presents container-style operations such as size, begin, end and at. Its size is part of its type. An array of three integers and an array of four integers are different types, which can be useful when an API requires a fixed shape.
The array object owns its elements and follows ordinary object lifetime. Copying it copies the element values according to their types. Unlike a vector, it does not dynamically grow or reallocate its storage. Initialize it deliberately: value initialization can zero arithmetic elements, while careless default initialization can leave arithmetic values uninitialized. Use at for checked runtime element access when that is the desired policy; operator[] requires a valid index.
Span borrows storage and carries an extent
A dynamic-extent span records a runtime size. A fixed-extent span has a size specified in its type. It can view contiguous storage from an appropriate array or vector without copying the elements. Passing a span by value is inexpensive because the view is small, but it does not transfer ownership or extend the storage's lifetime.
In a span of const int, the elements are read-only through that view. A const span of int instead makes the view object const while its element type remains mutable. These are different restrictions. Choose the element constness that matches the operation. A read-only sum function should accept a span of const int, allowing callers to pass mutable storage without giving the function permission to modify it.
Extent does not automatically enforce every access
In C++20, span's operator[] is not a general checked-access operation. The index must satisfy its precondition. Subview operations also have extent and offset preconditions. Validate runtime offsets before creating a subspan or indexing it. Do not demonstrate out-of-range access and infer safety from whether one build crashes.
A vector reallocation can invalidate a span into that vector. Erasing or moving elements can also invalidate the intended view. An array does not reallocate, but its destruction still leaves a dangling view. Keep spans local to a borrowing operation when possible. A class storing a span must document the external owner's required lifetime and any mutations that could invalidate the view.
Flat storage can express a two-dimensional shape
A row-major grid stores each row's elements consecutively. For a grid with columns columns, the flat position is row times columns plus column. That formula is valid only after checking both coordinates and ensuring the shape's arithmetic fits. In a fixed two-by-three example, the bounds and multiplication are small and explicit.
A nested array can also express fixed rows and columns, but one flat array makes it straightforward to create a contiguous row span. Decide whether the API returns an element, a row view or a whole-grid view. Returning a row span borrows the grid, so it must not outlive the grid object. Do not flatten unrelated pointers or assume a collection of separately allocated rows forms one contiguous block.
Choose ownership before convenience
An API that needs to retain data can accept or construct an owning collection rather than storing a borrowed span whose owner is unknown. A synchronous operation that only inspects a contiguous range is a good fit for a read-only span. A linked list is not contiguous and cannot become a span simply by passing its first element and count.
Tests should cover empty dynamic spans, fixed sizes, boundary coordinates and invalid row indices. If a function permits an empty view, ensure it does not dereference the first element unconditionally. Keep version requirements visible: methods added to span in later standards are not part of the C++20 baseline just because they appear in a current working draft.
Official references
C++ Array, Span and Multidimensional Data Examples
Each example shows the code, the output it produces and what each part does.
Read a fixed array through a span
This complete C++20 program sums an array and then a known valid subview. The sum function neither owns nor mutates the storage.
The offsets are fixed valid values in this fixture. A user-supplied offset would need checking. The small quantities make the int sum safe here; a general accumulator still needs its numeric range contract.
#include <array>
#include <iostream>
#include <span>
int sum(std::span<const int> values) {
int result = 0;
for (int value : values)
result += value;
return result;
}
int main() {
std::array<int, 4> quantities{2, 4, 6, 8};
std::span<const int> all(quantities);
std::cout << "All: " << sum(all) << '\n';
std::cout << "Middle: " << sum(all.subspan(1, 2)) << '\n';
std::cout << "Count: " << all.size() << '\n';
}All: 20
Middle: 10
Count: 4C++ Array, Span and Multidimensional Data in Real Projects
Worked exercise: sort one checked row
The task is to store two rows of three values and sort only the second row. The grid owns its elements, validates the runtime row index and returns a fixed-extent borrowing span. The sort operation modifies the existing row in place.
#include <algorithm>
#include <array>
#include <cstddef>
#include <iostream>
#include <span>
#include <stdexcept>
class Grid {
std::array<int, 6> values_{1, 2, 3, 9, 4, 7};
public:
std::span<int, 3> row(std::size_t index) {
if (index >= 2)
throw std::out_of_range("row index");
return std::span<int, 3>(values_.data() + index * 3, 3);
}
int at(std::size_t row, std::size_t column) const {
if (row >= 2 || column >= 3)
throw std::out_of_range("grid coordinate");
return values_.at(row * 3 + column);
}
};
int main() {
Grid grid;
auto selected = grid.row(1);
std::sort(selected.begin(), selected.end());
for (std::size_t column = 0; column < 3; ++column)
std::cout << grid.at(1, column) << '\n';
}4
7
9The row view does not allocate or copy data. The grid remains alive through sorting and printing. Requesting row two would throw before pointer arithmetic creates an invalid row view.
C++ Array, Span and Multidimensional Data FAQ
Does span keep its array or vector alive?
No. It borrows existing storage and does not own the elements. The caller must keep that storage alive and valid for every use of the view. A vector's reallocation can invalidate a span even while the vector object itself remains alive. Prefer short borrowing scopes or an explicit owner contract when a view is stored.
Is const span<int> a read-only element view?
Not in the same way as span<const int>. Const on the span object restricts the view object, while int remains the mutable element type. Use span of const elements when the function must not modify them through the view. This distinction mirrors pointer qualification and is important when designing an observer's parameter.
Why use array rather than vector for a fixed shape?
The compile-time extent expresses the shape in the type and storage does not dynamically grow. Container-style operations make it easier to use than a raw array in many interfaces. A vector is appropriate when the number of elements changes at runtime. Neither choice automatically validates user indices or establishes the correct business maximum.
Should a two-dimensional view always use mdspan?
C++23 mdspan offers a dedicated multidimensional view abstraction, but a small explicit fixed grid can be clear under a C++20 baseline. Choose from the project's standard level, layout requirements and expected operations. A view facility still needs valid storage and shape information. It does not remove ownership or lifetime responsibilities from the surrounding design.
C++ Array, Span and Multidimensional Data Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
A runtime grid row must be exposed safely. Which sequence fits?
C++ Array, Span and Multidimensional Data Quiz
2 questions to check what you learned.
Which object owns elements in the main example?
- The sum function's span parameter.
- The function retains ownership by accepting the span by value.
- The subspan returned temporarily.
- The std::array.
A span points into a vector that reallocates. What should be assumed?
- The old view may be invalid and must not be used without reestablishing validity.
- Span automatically follows the new allocation.
- The vector cannot reallocate while a span exists.
- Const element type transfers ownership.