std::string_view

A std::string_view is a pointer and a length: it names a range of characters that somebody else owns. Copying a view copies two numbers, not the text, and a substring of a view is another view into the same characters. That makes it the right parameter type for a function that only reads text.

The price is that a view does not own anything, so it must not outlive the string it points into. This lesson shows the free operations first and ends with the two mistakes that a view makes possible: reading past the end of life, and expecting the null-terminated C string that a view does not have.

A view over an existing string

A view is constructed from a std::string, a literal or another view, and it borrows the characters. size() and data() describe the range; nothing is allocated and nothing is copied.

substr on a view returns another view. The program cuts "quick" out of the sentence with one index and one length, and the result is five characters pointing into the same buffer that text owns.

Because the view is cheap to copy, functions take it by value: std::string_view text costs two words at the call and accepts anything that has characters and a length.

#include <iostream> #include <string> #include <string_view> int main() { std::string text = "the quick brown fox"; std::string_view view = text; std::string_view word = view.substr(4, 5); std::cout << word << ' ' << word.size() << '\n'; std::cout << "view size: " << view.size() << '\n'; std::cout << "same buffer: " << (word.data() == view.data() + 4) << '\n'; }

g++ -std=c++23 -Wall -Wextra what.cpp -o what
./what

quick 5
view size: 19
same buffer: 1

A parameter that accepts both

A function that takes std::string_view can be called with a std::string, with a string literal and with a character array, and the compiler builds the view at the call site without allocating. One parameter type covers every kind of text.

The comparison with a const std::string& parameter is about ownership, not speed: the reference binds to an existing string and refuses a literal unless a temporary is made, while the view accepts everything and copies nothing.

The program passes a named string and a literal to the same function. Both calls print a length, and no std::string was constructed for the second one.

#include <iostream> #include <string> #include <string_view> std::size_t length(std::string_view text) { return text.size(); } int main() { std::string owned = "owned string"; std::cout << length(owned) << ' ' << length("literal") << '\n'; }

g++ -std=c++23 -Wall -Wextra params.cpp -o params
./params

12 7

A view in a constant expression

A string_view constructed from a literal is a constant expression, so it can be used in constexpr code. The function below counts one character in a view and is evaluated at compile time for the static_assert, then again at run time for the printed value.

This is how a table of names or a fixed keyword list can live in the binary without a std::string allocation: an array of string_view is an array of pointer and length pairs.

The characters must still outlive the view. A literal has static storage duration, so a view over it is safe for the whole program; a view over a local std::string is not.

#include <iostream> #include <string_view> constexpr std::size_t count_char(std::string_view text, char wanted) { std::size_t count = 0; for (char c : text) { if (c == wanted) { ++count; } } return count; } int main() { constexpr std::string_view word = "mississippi"; static_assert(count_char(word, 's') == 4, "four s"); std::cout << count_char(word, 'i') << '\n'; }

g++ -std=c++23 -Wall -Wextra constexpr.cpp -o constexpr_view
./constexpr_view

4

From a view back to a string

Returning a view from a function is safe only when the characters outlive the caller's use. Returning a std::string is the safe default: it owns its characters, and the caller can keep it for as long as it likes.

std::string has a constructor that takes a string_view, so converting is explicit in the source: std::string(view). The program transforms a view into a new string, letter by letter, and prints the copy and its size.

A function that takes a view and returns a string is the usual pattern for text processing: the input costs nothing to pass, and the output is owned by the caller.

#include <iostream> #include <string> #include <string_view> std::string to_upper(std::string_view text) { std::string result; for (char c : text) { if (c >= 'a' && c <= 'z') { result += static_cast<char>(c - 'a' + 'A'); } else { result += c; } } return result; } int main() { std::string_view view = "mixed Case"; std::string copy = to_upper(view); std::cout << copy << ' ' << copy.size() << '\n'; }

g++ -std=c++23 -Wall -Wextra conversion.cpp -o conversion
./conversion

MIXED CASE 10

A view is not a C string

A view has data() and size(), and no guarantee that the characters end with a null byte. That is why it has no c_str(): a function that takes a const char* would read past the end of the view.

The program tries to call c_str on a view, and the compiler reports that the member does not exist. The fix is to build a std::string from the view and take its c_str(), or to keep the original std::string and pass that.

The same rule explains the other dangling case: a view of a temporary string is a view of memory that is gone at the end of the statement. Store the std::string, not the view, when the text has to survive the call.

#include <cstdio> #include <string_view> int main() { std::string_view view = "text"; std::puts(view.c_str()); }

g++ -std=c++23 -Wall -Wextra cstr.cpp -o cstr

cstr.cpp: In function 'int main()':
cstr.cpp:7:20: error: 'using std::string_view = class std::basic_string_view<char>' {aka 'class std::basic_string_view<char>'} has no member named 'c_str'; did you mean 'const char* std::basic_string_view<char>::_M_str'? (not accessible from this context)
7 | std::puts(view.c_str());
| ^~~~~
In file included from cstr.cpp:2:
/usr/include/c++/15.2.0/string_view:591:21: note: declared private here
591 | const _CharT* _M_str;
| ^~~~~~

Exercises

  1. Change length() in params.cpp to take const std::string& and call it with a literal. Does it compile, and how many strings are created?
  2. Write a function first_word(std::string_view) that returns a view up to the first space, and call it on a std::string that lives longer than the result.
  3. Build a std::string from the view in cstr.cpp and call c_str() on the string. Which object owns the characters now?

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What does a std::string_view store?

    Answer

    A pointer to characters and a length

    The two words are all a view has, which is why copying it is cheap.

  2. What does view.substr(1, 3) return?

    Answer

    Another view into the same characters

    The substring of a view is a view, and no characters are copied.

  3. What is the danger of a view?

    Answer

    It can outlive the string it points into

    The view does not own the characters, so the owner must outlive every view on it.

  4. Why take a std::string_view parameter by value?

    Answer

    The copy is two words, smaller than a reference plus a call

    The view is a small trivially copyable object, so passing it by value is the normal form.

  5. What does this program print? #include <iostream> #include <string> #include <string_view> int main() { std::string text = "the quick brown fox"; std::string_view view{text}; std::string_view word = view.substr(4, 5); std::cout << word << ' ' << word.size() << " / view size: " << view.size() << " / same buffer: " << (word.data() == text.data() + 4) << '\n'; }

    Answer

    quick 5 / view size: 19 / same buffer: 1

    The word is five characters, the whole line is nineteen, and the view points into the original buffer.

  6. What does this program print? #include <iostream> #include <string_view> size_t count(std::string_view text) { return text.size(); } int main() { std::cout << count("hello world!") << ' ' << count(std::string_view{"letters"}); }

    Answer

    12 7

    The function accepts a literal and a view, and the two lengths are 12 and 7.

  7. What does this program print? #include <iostream> #include <string_view> constexpr std::string_view text = "four"; static_assert(text.size() == 4); int main() { std::cout << text.size(); }

    Answer

    4

    The view is formed over a literal at compile time, and its size is four.

  8. What does this program print? #include <cctype> #include <iostream> #include <string> #include <string_view> int main() { std::string_view view = "Mixed Case"; std::string text{view}; for (char& c : text) c = static_cast<char>(std::toupper(static_cast<unsigned char>(c))); std::cout << text << ' ' << text.size(); }

    Answer

    MIXED CASE 10

    The view is turned into a string, the letters are uppercased, and the length is ten.

  9. What does the compiler say about view.c_str()?

    Answer

    the class has no member named 'c_str'

    A view does not promise a terminator, so it cannot hand out a C string.

  10. Why is a view not a C string?

    Answer

    Its characters are not guaranteed to end with a null

    C functions read until a terminator, and a view may point into the middle of a larger buffer.

The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.

Article author: Arthur Isaev