AlgoMaster Logo

std::string_view (C++17)

Medium Priority18 min readUpdated September 21, 2026
Listen to this chapter
Unlock Audio

std::string_view is a non-owning, read-only view over a sequence of characters that lives somewhere else (C++17). It is a pair of (pointer, length) that points into a std::string, a string literal, a char array, or any other contiguous run of characters. It provides the read-only API of a string without paying for a copy. The header is <string_view>, and the type lives in namespace std.

What Is a string_view

A std::string_view does not own characters. It points at them. Internally, every implementation stores two things: a pointer to the first character and a length. That is all. No allocation, no buffer, no null terminator guarantee.

The view prints the same characters as the underlying string because it points at the same memory. The view itself is tiny, just two machine words. The string owns the buffer; the view borrows it.

This is the picture in memory. The std::string owns the character buffer on the heap. The std::string_view is two fields on the stack that record where to look and how many characters to read.

The view and the string both reach the same characters, but only the string is responsible for that memory. Destroying the view does nothing to the buffer. Destroying the string leaves the view pointing at freed memory, which is the central pitfall covered later.

One way to think about it: a string is a buffer with a length, while a string_view is a length with a pointer to someone else's buffer.

Constructing One

A std::string_view can be built from anything that has a contiguous block of characters and a known length. The standard library provides constructors for all the common cases.

Four ways to get a view, four sources. The std::string case is the most common in real code. The literal case is what makes std::string_view so convenient for function parameters, since "some text" can be passed directly without wrapping it in a std::string. The (pointer, length) constructor is the only way to view part of a buffer that is not null terminated. v4 reads only the first 5 characters of buffer and stops, even though buffer has 7 characters total.

A fifth source comes up often: an existing view, sliced with substr.

substr on a string_view returns another string_view that points into the same buffer. No new memory is allocated. category is still looking at the original "PROD-2026-AUDIO-001" characters, just at a different offset and with a shorter length.

std::string_view::substr is O(1). It only adjusts the pointer and the length. std::string::substr is O(n) because it allocates a new string and copies the characters across.

That O(1) substring is one of the largest gains from std::string_view. Parsing a long string into many small pieces with views avoids dozens of small allocations.

What You Can Do With a View

A std::string_view exposes the read-only half of the std::string API. If a member function on std::string only reads characters, there is almost always a matching one on string_view.

size, empty, operator[], at, front, back, find, rfind, find_first_of, compare, starts_with (C++20), ends_with (C++20). All of these read the underlying characters without modifying them.

Comparison operators work too, and they compare lexicographically just like std::string.

The comparison reads characters from both views and reports the result. This is useful for sorting product codes, searching by name, and similar lookup tasks.

Mutating operations are not available. There is no push_back, no append, no operator+=, no clear that empties a buffer. The reason is simple: the view does not own the buffer, so it has no authority to grow it, shrink it, or change the characters in it. The only "mutations" a view permits are the ones that adjust its own pointer and length, not the underlying data.

remove_prefix and remove_suffix change what the view points at, not what is stored in memory. The underlying "Headphones" is unchanged. The view's internal pointer moves forward by 3, and its length shrinks by 5 in total.

A more realistic E-Commerce example: check whether a product code has a category prefix.

Both parameters are std::string_view, so the function can be called with literals, strings, or other views without any copying. Inside the function, substr returns another view, the equality check is a character-by-character compare, and no heap activity happens anywhere. (From C++20 onward, code.starts_with(prefix) is even shorter.)

Why Pass by string_view

The motivating use case for std::string_view is read-only string parameters. Before C++17, the standard advice was to take const std::string&. That works, but it has a sharp edge: the caller must already have a std::string. A string literal, a C-string, or a substring of another string all force a conversion that allocates.

A simple search function written with the old style:

Every call passes "less" or "Wired" as a string literal. The function signature says const std::string&, so the compiler builds a temporary std::string for each call. Each temporary allocates a buffer, copies the characters, and is destroyed once the function returns. For two short calls in a small program, the cost is invisible. For a search loop that calls nameContains a million times with literals, those temporary allocations dominate the runtime.

The string_view version takes the same kind of arguments without the allocation.

The function now accepts std::string, string literals, and even substrings of other views, all without copying anything. The body is identical; only the parameter types changed. productName.find(needle) works because find is part of the view's read-only API.

Pass by value, not by const reference. A std::string_view is 16 bytes, which fits comfortably in registers. There is no copy of the underlying characters, just a copy of the (pointer, length) pair. The const-reference indirection would cost more than it saves.

Copying a std::string_view is O(1) and copies 16 bytes. Pass it by value, the same way an int is passed. Using const std::string_view& is a small pessimization for no benefit.

A default rule for read-only string parameters:

Use caseOld C++ (pre-C++17)Modern C++ (C++17+)
Read-only string parameterconst std::string&std::string_view
Accepts string literals without copyNo (forces a std::string to be built)Yes
Accepts std::stringYesYes
Accepts char* C-stringYes (via temporary)Yes
Accepts a substringOnly by copyingYes, in O(1)

The view wins on every row except one, which is the lifetime question coming up next.

The Dangling Pitfall

std::string_view does not own the characters it points at. If those characters go away, the view becomes dangling, and reading through it is undefined behavior. This is the part of string_view that causes the most bugs.

A simple wrong example: a function that builds a string locally and returns a view of it.

What is wrong with this code?

The local string label is destroyed when buildLabel returns. The returned view still records a pointer to where the string used to live, but that memory has been freed. Printing v reads from freed memory, which is undefined behavior. The program might print the right text, arbitrary data, nothing, or crash. None of those outcomes prove anything; the code is broken either way.

The picture after the function returns looks like this. The string's buffer is gone, but the view still records its old address.

The fix is to return a std::string (which owns its buffer and survives the return) instead of a std::string_view:

The caller now gets a real string they own. No dangling view.

Another sharp case is storing a view of a temporary.

What is wrong with this code?

customerName() returns a std::string by value. That temporary lives until the end of the full expression, then is destroyed. name records a pointer into the temporary's buffer, but the temporary is gone the moment the next statement starts. The cout line reads from freed memory.

This is tricky because storing a const std::string& extends the temporary's lifetime to the lifetime of the reference, which is a special rule for references. std::string_view is not a reference, so that rule does not apply. The view's lifetime has nothing to do with the temporary's.

The fix is to store a std::string, not a view, when the source is a temporary:

A general way to think about it: ask "who owns the characters this view points at, and how long do they live?" If the owner outlives the view, the view is safe. If the owner dies first, the view is dangling.

Source of the viewOwnerOwner's lifetimeSafe?
String literalRead-only program dataWhole programYes
Long-lived std::stringThe stringUntil the string goes out of scopeYes, while the string lives
Local std::string, returned by viewThe local stringUntil the function returnsNo, dangles after return
Temporary std::string (e.g. function return value)The temporaryUntil end of full expressionRisky, almost always wrong to store
Substring of an existing live viewThe original bufferSame as that bufferYes, same lifetime

string_view vs const string& vs std::string

When choosing the parameter type for a function that takes a string, there are three common candidates. Each has its own appropriate use.

Parameter typeCopies characters?Accepts literals without alloc?Can be null?When to pick
std::string_viewNoYesNo (use .empty() for emptiness)Read-only access, any string source, modern default
const std::string&NoNo (allocates a temp)NoThe function specifically needs a std::string (e.g., to call .c_str() on a guaranteed null-terminated buffer, or to pass to an API that takes const std::string&)
std::string (by value)YesYes (constructed from each source)NoThe function needs to keep or modify its own copy of the string

The first two rows look almost identical, but the difference matters. A std::string_view is a thin handle that costs nothing to build from any string source. A const std::string& is a reference to a real std::string, so any non-string source has to be converted to one first.

There is one important subtlety that pushes some code toward const std::string& even today: `std::string_view` is not guaranteed to be null terminated. A std::string's internal buffer is always null terminated (the \0 lives one past the last character), which is why std::string::c_str() is a constant-time read. A std::string_view, on the other hand, is a pointer plus a length. The character at view.data() + view.size() could be anything, because the view might be pointing into the middle of a longer buffer.

When a std::string_view is on hand and a C API takes a const char*, passing view.data() is not safe. The bytes immediately after the view's last character are not part of the view. The safe move is to build a temporary std::string from the view:

That is the one corner where const std::string& is still the more honest signature. If a function requires a guaranteed null-terminated buffer (because it forwards to a C API), taking const std::string& forces the caller to materialize a real string at the call site, which makes the allocation visible and avoids hiding it inside the function. For everything else, prefer std::string_view.

Building a std::string from a std::string_view (e.g., std::string s(view);) allocates and copies all the characters. It is O(n). Do this only when a null-terminated buffer is needed or the data has to be owned.

When NOT to Use string_view

std::string_view is the default for read-only string parameters, but it is not the right tool everywhere. The dangling-pitfall section gives the underlying rule; the common anti-patterns are worth spelling out.

Don't store a `std::string_view` as a class member that outlives the source data.

A CartItem built from a literal is fine, because the literal lives for the whole program. A CartItem built from a local std::string is a time bomb, since the view will dangle the moment that string is destroyed. Class members usually need to own their data, so prefer std::string name_ for members, and accept std::string_view in the constructor only if you intend to copy it.

Don't return a `std::string_view` from a function that builds a transient string.

If the function constructs a new string (concatenation, formatting, conversion), returning a view of it is the dangling case from earlier. Return a std::string so the caller owns the result.

Don't use it as a return type for "first matching element" lookups when the matching element is computed, not borrowed.

If the function returns a view into existing storage (e.g., a substring of an input), std::string_view is fine. If it returns a freshly composed string, return std::string.

Don't use it where the source string might be modified during the view's lifetime.

Any non-const operation on a std::string (assigning to it, appending, calling reserve or resize, clearing) can invalidate every existing view of it. Appending to the source while a view is live is a bug, whether or not the program happens to print the right thing.

Don't use it for non-character buffers. That is what std::span (C++20) is for. std::string_view is specifically a view of characters with the string-style API. A view of integers, or of arbitrary objects, is not what string_view is built for.

A short decision summary:

  • Read-only string parameter: std::string_view, pass by value.
  • Stored member or owned data: std::string.
  • Calling a C API that requires null termination: build a std::string first, or use const std::string& as the parameter.
  • The function needs to modify the caller's string: std::string&.

That covers most real choices.

Quiz

std::string_view Quiz

9 quizzes