How to Use absl::Span for Safer Array and Pointer Passing in C++

Use absl::Span<T> to replace raw pointer-size pairs with a lightweight, non-owning view that provides bounds-checked access and accepts any contiguous container without copying data.

The Abseil C++ library provides absl::Span as a safer alternative to passing raw pointers and lengths to functions. Defined in absl/types/span.h, this class template enables APIs that work seamlessly with std::vector, C arrays, absl::InlinedVector, and other contiguous sequences while preventing common pointer-size mismatches and out-of-bounds errors.

Core Design of absl::Span

absl::Span is a non-owning view that stores only a pointer (ptr_) and a length (len_). According to the class comment at line 20 of absl/types/span.h, the data it points to must outlive the Span instance. This design makes Span as lightweight as a raw pointer pair but with strict type safety and bounds checking.

The class distinguishes between mutable and const access through template specialization. Use absl::Span<const T> for read-only views and absl::Span<T> when elements need modification.

Constructing absl::Span Objects

The header provides multiple construction strategies starting at line 25, allowing implicit or explicit conversion depending on mutability requirements. Internal SFINAE utilities in absl/types/internal/span.h enable automatic detection of containers providing data() and size() methods.

From Pointer-Size Pairs

The explicit constructor Span(pointer, size) creates a span from a raw pointer and element count. This is the lowest-level constructor used when interfacing with C APIs.

int* data = new int[100];
absl::Span<int> span(data, 100);

From C Arrays and Containers

absl::Span accepts C arrays directly through a template constructor Span(T (&arr)[N]), deducing the size at compile time. For standard containers providing data() and size() methods, implicit conversion works for Span<const T>, while Span<T> requires explicit construction to prevent accidental mutation.

std::vector<int> vec = {1, 2, 3};
absl::Span<const int> read_only = vec;  // implicit
absl::Span<int> mutable_span(vec.data(), vec.size());  // explicit

Factory Helpers: MakeSpan and MakeConstSpan

To avoid verbose template arguments, use the factory helpers declared around line 86 in absl/types/span.h. absl::MakeSpan deduces the element type automatically for mutable spans, while absl::MakeConstSpan creates read-only views.

int arr[5] = {1, 2, 3, 4, 5};
auto span = absl::MakeSpan(arr);        // absl::Span<int>
auto cspan = absl::MakeConstSpan(arr);  // absl::Span<const int>

Bounds-Checked Access and Sub-Views

Safe Element Access

The implementation provides two access modes with different safety guarantees. operator[] uses hardening assertions (defined in absl/base/internal/hardening.h) for debug builds, while at() performs runtime bounds checking and throws std::out_of_range on failure (see lines 33-45).

absl::Span<int> span = GetData();
int first = span[0];        // Hardened assert in debug
int safe = span.at(0);      // Throws if out of range

Creating Sub-Views Without Copying

The member functions subspan, first, and last return new absl::Span instances referencing subsets of the original data (lines 44-97). These operations only adjust the internal pointer and length, performing no heap allocation or element copying.

absl::Span<int> data = GetLargeBuffer();
auto middle = data.subspan(10, 20);  // Elements 10-29
auto head = data.first(5);           // First 5 elements
auto tail = data.last(5);            // Last 5 elements

Practical Usage Examples

The following examples demonstrate typical patterns for safer array passing in the abseil-cpp codebase.

Read-Only Function Parameters

Accepting absl::Span<const T> allows a single function to process vectors, arrays, or initializer lists without overloads.

#include "absl/types/span.h"
#include <vector>
#include <iostream>

void PrintSum(absl::Span<const int> values) {
  int sum = 0;
  for (int v : values) sum += v;
  std::cout << "sum = " << sum << '\n';
}

// Works with any contiguous sequence
void Example() {
  std::vector<int> vec = {1, 2, 3, 4};
  PrintSum(vec);                    // Implicit conversion
  
  int arr[3] = {5, 6, 7};
  PrintSum(arr);                    // Array decay
  
  PrintSum({8, 9, 10});             // Initializer list
}

Mutable Span Operations

When mutation is required, use absl::MakeSpan to obtain a mutable view, then modify elements safely.

void DoubleValues(absl::Span<int> values) {
  for (int& v : values) v *= 2;
}

void Example() {
  int arr[5] = {1, 2, 3, 4, 5};
  auto span = absl::MakeSpan(arr);
  span[0] = 10;                     // Safe indexed access
  DoubleValues(span);
}

Working with Sub-Views

Sub-span operations enable zero-cost slicing of data ranges.

void ProcessChunk(absl::Span<const float> data) {
  auto header = data.first(4);      // First 4 elements
  auto payload = data.subspan(4);   // Remaining elements
  
  // Process separately...
}

C++20 Ranges Interoperability

When compiling with C++20, absl::Span satisfies the std::ranges::view and std::ranges::borrowed_range concepts (lines 94-101 of absl/types/span.h). This enables seamless use with standard range algorithms and views without copying underlying data.

// C++20 only
#include <ranges>
#include <algorithm>

absl::Span<int> data = GetData();
auto result = data | std::views::filter([](int x) { return x > 0; });

Summary

  • Use absl::Span<const T> for read-only function parameters to accept any contiguous container without copying.
  • Prefer factory helpers absl::MakeSpan and absl::MakeConstSpan over explicit template arguments when constructing spans.
  • Ensure lifetime safety by guaranteeing the underlying data outlives the Span instance, as the view stores only a pointer and length.
  • Leverage bounds checking through at() for runtime safety or operator[] for debug-only hardening via absl/base/internal/hardening.h.
  • Create zero-cost sub-views using subspan, first, and last to reference data slices without allocation.

Frequently Asked Questions

What's the difference between absl::Span and std::span?

absl::Span predates the C++20 standard and provides similar functionality with additional Abseil-specific hardening. While std::span is standard C++20, absl::Span includes hardened assertions via absl/base/internal/hardening.h and integrates with Abseil's type system. Codebases using Abseil typically prefer absl::Span for consistency across C++14, C++17, and C++20 projects.

Can I store an absl::Span as a class member?

Yes, but you must ensure strict lifetime management. Because absl::Span is non-owning and stores only ptr_ and len_ (as defined at line 20 of absl/types/span.h), storing it in a class creates a potential dangling reference if the underlying container is destroyed or reallocated. Only store spans when the data lifetime is guaranteed to exceed the class instance lifetime.

How do I handle null pointers with absl::Span?

absl::Span supports null pointers with zero length, with nullability annotations defined in absl/base/nullability.h. You can construct an empty span with absl::Span<T>(nullptr, 0) or simply absl::Span<T>(). The empty() method checks for zero length, and data() may return null for empty spans. Always verify !span.empty() before dereferencing span.data().

Is absl::Span compatible with C++20 ranges?

Yes. As implemented in lines 94-101 of absl/types/span.h, absl::Span satisfies both std::ranges::view and std::ranges::borrowed_range concepts when <ranges> is available. This allows absl::Span to work with standard range algorithms, views, and pipelines without copying data or modifying the Abseil library code.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →