string_view and span

std::string_view and std::span are lightweight, non-owning views: a pointer plus a length that refers to text or elements stored somewhere else. They make functions accept many kinds of input cheaply — but because they don't own their data, you must make sure the data outlives the view.

std::string_view

A function taking const std::string& forces callers with a string literal to build a temporary std::string. A std::string_view parameter accepts literals, std::strings and parts of strings without copying anything:

C++
#include <iostream>
#include <string>
#include <string_view>

std::size_t count_char(std::string_view text, char c) {
    std::size_t n = 0;
    for (char ch : text) if (ch == c) ++n;
    return n;
}

int main() {
    std::string sentence = "she sells sea shells";
    std::cout << count_char(sentence, 's') << ' '
              << count_char("mississippi", 'i') << ' '
              << count_char(std::string_view{sentence}.substr(0, 9), 's') << '\n';
}
Output
6 4 3

Pass string_view by value — it's just two words of data, cheaper to copy than a reference is to follow.

Cheap substrings

substr, remove_prefix and remove_suffix on a view just adjust its pointer and length — no allocation, unlike std::string::substr:

C++
#include <iostream>
#include <string_view>
#include <vector>

std::vector<std::string_view> split(std::string_view text, char sep) {
    std::vector<std::string_view> parts;
    while (true) {
        auto pos = text.find(sep);
        parts.push_back(text.substr(0, pos));
        if (pos == std::string_view::npos) break;
        text.remove_prefix(pos + 1);
    }
    return parts;
}

int main() {
    for (auto field : split("2026-10-08,Lisbon,,22.5", ',')) {
        std::cout << '[' << field << "] ";
    }
    std::cout << '\n';
}
Output
[2026-10-08] [Lisbon] [] [22.5]

All the views in the vector point into the original string literal, which lives for the whole program — so this is safe.

The danger: dangling views

A view must not outlive the string it looks at. Viewing a temporary std::string that's destroyed at the end of the statement leaves the view pointing at freed memory:

C++
#include <iostream>
#include <string>
#include <string_view>

std::string make_name() { return "a temporary string, long enough to be on the heap"; }

int main() {
    std::string_view v = make_name();      // the string dies at the end of this line!
    std::cout << v.size() << '\n';
}
Compiler warnings
main.cpp:8:26: warning: object backing the pointer will be destroyed at the end of the full-expression [-Wdangling-gsl]

Rules of thumb: use string_view for parameters; be careful storing it in variables or class members; never return a view of a local string. When you need to keep the text, convert it: std::string{view}.

std::span

std::span<T> (C++20) is the same idea for contiguous elements: it can view a std::vector, a std::array, a C array or part of any of them:

C++
#include <array>
#include <iostream>
#include <span>
#include <vector>

double average(std::span<const double> values) {
    double sum = 0;
    for (double v : values) sum += v;
    return values.empty() ? 0 : sum / values.size();
}

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

int main() {
    std::vector<double> week{14.5, 17.0, 12.25, 19.75, 16.0, 13.5, 18.25};
    std::array<double, 3> trio{1.0, 2.0, 3.0};
    double c_array[] = {10, 20};

    std::cout << average(week) << ' ' << average(trio) << ' ' << average(c_array) << '\n';
    std::cout << average(std::span{week}.first(3)) << ' ' << average(std::span{week}.last(2)) << '\n';

    std::vector<int> counts{1, 2, 3};
    double_all(counts);
    for (int c : counts) std::cout << c << ' ';
    std::cout << '\n';
}
Output
15.8929 2 15
14.5833 15.875
2 4 6

std::span<const T> is a read-only view; std::span<T> lets the function modify the elements, but never add or remove them.

When to use which

ParameterAcceptsUse when
std::string_viewLiterals, std::string, substringsReading text
const std::string&std::string (literals via a temporary)You need a std::string or a null-terminated c_str()
std::span<const T>Vectors, arrays, C arrays, slicesReading a sequence of elements
const std::vector<T>&Only vectorsYou need vector-specific features

One catch: a string_view isn't guaranteed to be null-terminated, so don't pass view.data() to C functions that expect a C string.

Try it yourself

Write trim, which returns a string_view of its argument without leading and trailing spaces — no copying.

Try it yourself · main.cpp
Edit the code, copy it into main.cpp, then compile and run it with c++ -std=c++20 -Wall main.cpp -o main && ./main. How to set up a C++ compiler
Output
[hello world]
[no-spaces]
[]