Skip to content

C++ std::span

std::span<T> is a small, non-owning view over a contiguous sequence of T objects. It carries both a pointer and an element count, making it a clearer function parameter than separate pointer-and-size arguments.

Simple example

#include <array>
#include <iostream>
#include <span>

void double_values(std::span<int> values)
{
    for (int& value : values)
        value *= 2;
}

int main()
{
    std::array<int, 4> values{1, 2, 3, 4};
    double_values(values);

    for (const int value : values)
        std::cout << value << ' ';
}

The span refers to the elements owned by values, so the function modifies the original array.

Compile with C++20 or newer:

g++ -std=c++20 -Wall -Wextra -pedantic example.cpp -o example
./example
2 4 6 8

What can create a span?

A span can view contiguous storage such as a built-in array, std::array, or std::vector:

1
2
3
4
5
6
7
int raw[]{1, 2, 3};
std::array<int, 3> fixed{4, 5, 6};
std::vector<int> dynamic{7, 8, 9};

std::span<int> raw_view{raw};
std::span<int> fixed_view{fixed};
std::span<int> dynamic_view{dynamic};

Containers such as std::list are not supported because their elements are not stored contiguously.

Read-only and writable spans

Use std::span<const T> when a function only reads elements:

1
2
3
4
5
6
7
8
9
int sum(std::span<const int> values)
{
    int total{};

    for (const int value : values)
        total += value;

    return total;
}

A writable container can be passed to either std::span<T> or std::span<const T>. A const container can only be viewed through std::span<const T>.

Dynamic and fixed extent

The default span has a size known at runtime:

std::span<int> values;

A fixed-extent span includes the required element count in its type:

void update_rgb(std::span<int, 3> channels);

Fixed extent is useful when an operation always requires an exact number of elements. Prefer dynamic extent for general-purpose sequence functions.

Access and subviews

A span supports iteration, indexing, and lightweight subviews:

1
2
3
4
5
std::span<int> values{buffer};

const auto first_three = values.first(3);
const auto last_two = values.last(2);
const auto middle = values.subspan(1, 2);

Useful operations include:

Operation Purpose
size() Return the number of elements.
size_bytes() Return the viewed size in bytes.
empty() Check whether the span has no elements.
data() Return a pointer to the first element.
front() / back() Access the first or last element.
first(), last(), subspan() Create a smaller view without copying elements.

Indexing is not bounds checked

Check sizes before using operator[], front(), back(), or a runtime subview count. A span knows its size, but these operations do not automatically make invalid access safe.

Possible uses

  • Accept arrays, std::array, and std::vector through one function interface.
  • Replace a pointer-and-count parameter such as process(data, size).
  • Pass a read-only sequence with std::span<const T>.
  • Modify a caller-owned buffer without copying it.
  • Work on a window of a larger buffer using subspan().
  • Pass contiguous data to a C API through data() and size().
  • Inspect an object's byte representation with std::as_bytes().
1
2
3
4
void send(std::span<const std::byte> bytes);

std::array<int, 4> values{1, 2, 3, 4};
send(std::as_bytes(std::span{values}));

std::as_bytes() exposes object representation; it does not define a portable serialization format.

Lifetime rules

A span does not own or extend the lifetime of its elements. The viewed storage must remain alive and must not move while the span is used.

1
2
3
4
5
std::span<int> invalid_view()
{
    int local[]{1, 2, 3};
    return local; // Wrong: local is destroyed on return
}

Operations that reallocate a vector also invalidate spans into that vector:

1
2
3
4
std::vector<int> values{1, 2, 3};
std::span<int> view{values};

values.push_back(4); // May reallocate; view may now dangle

Recreate the span after an operation that may replace or reallocate its underlying storage.

When not to use a span

  • Use a container when the function must own or resize the sequence.
  • Use std::string_view for read-only character strings.
  • Use an iterator pair or a range when the data is not contiguous.
  • Use a smart pointer when ownership must be shared or transferred.

Quiz

1. What does the function modify?

1
2
3
4
5
void clear(std::span<int> values)
{
    for (int& value : values)
        value = 0;
}
Choose one answer:

2. Which parameter accepts readable data from both const and non-const contiguous containers?

Choose one answer:

3. Why can view become invalid?

1
2
3
std::vector<int> values{1, 2, 3};
std::span<int> view{values};
values.push_back(4);
Choose one answer:

Further reading