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:
#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'; }
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:
#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'; }
[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:
#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'; }
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:
#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'; }
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
| Parameter | Accepts | Use when |
|---|---|---|
std::string_view | Literals, std::string, substrings | Reading 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, slices | Reading a sequence of elements |
const std::vector<T>& | Only vectors | You 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.
[hello world] [no-spaces] []