Using std::string_view
Pass string data efficiently without copying using lightweight string views.
What Is std::string_view?
std::string_view is a read-only handle onto characters that already exist somewhere else. It lives in the <string_view> header and arrived with C++17. Creating one never allocates memory and never copies characters, so passing text around through views costs almost nothing.
Everything about the type follows from one sentence: a view reads borrowed characters and can never write them.
#include <iostream>
#include <string_view>
void logObservation(std::string_view entry)
{
std::cout << "> " << entry << '\n';
}
int main()
{
logObservation("Andromeda Galaxy, faint core visible");
return 0;
}
> Andromeda Galaxy, faint core visible
The parameter entry is not a string. It is a way of reading one. No storage was allocated for it, and not one character of that literal was duplicated.
The Copy That Never Happens
Start with the version that does the copying, so the saving is visible by comparison. Here the parameter is a std::string, which means the function gets its own private duplicate of every character:
#include <iostream>
#include <string>
void logObservation(std::string entry)
{
std::cout << "> " << entry << '\n';
}
int main()
{
std::string target { "Ring Nebula, small and grey" };
logObservation(target);
return 0;
}
> Ring Nebula, small and grey
Two separate copies of those characters exist during the call. The first was made when target was initialized from the string literal, and the second when entry was initialized from target. The function then prints the duplicate and throws it away.
Change one word in the parameter type and the second copy disappears:
#include <iostream>
#include <string>
#include <string_view>
void logObservation(std::string_view entry)
{
std::cout << "> " << entry << '\n';
}
int main()
{
std::string target { "Ring Nebula, small and grey" };
logObservation(target);
return 0;
}
> Ring Nebula, small and grey
Identical output, one fewer copy. entry now reads the characters that target is already holding.
The saving compounds depending on how the caller stores its text:
| Caller's variable | Parameter type | Copies of the characters |
|---|---|---|
std::string |
std::string |
2 |
std::string |
std::string_view |
1 |
std::string_view |
std::string_view |
0 |
The bottom row is where the opening example landed. A string literal already sits inside the compiled program, so a view of it, and any view copied from that view, duplicates nothing at all.
When a function only reads a string parameter, declare the parameter `std::string_view`. Reserve `std::string` parameters for functions that need their own copy to keep or to modify.
Which Strings Convert, and Which Do Not
C++ has three ways of spelling "some text", and the conversions between them are not symmetric. This table is worth memorising, because the asymmetry is the source of the one compile error learners hit with this type:
| You have | To std::string_view |
To std::string |
|---|---|---|
| C-style string literal | implicit, free | implicit, copies |
std::string |
implicit, free | implicit, copies |
std::string_view |
implicit, free | explicit only, copies |
Read the middle column: every string type slides into a view without ceremony, because doing so costs nothing. Read the right-hand column: the one conversion the compiler refuses to perform silently is view to std::string, because that is the only cell where a free thing would quietly turn into an expensive thing.
The permissive column means a view can be initialized from any of the three:
#include <iostream>
#include <string>
#include <string_view>
int main()
{
std::string_view fromLiteral { "Double Cluster" };
std::cout << fromLiteral << '\n';
std::string owned { "Orion Nebula" };
std::string_view fromString { owned };
std::cout << fromString << '\n';
std::string_view fromView { fromString };
std::cout << fromView << '\n';
return 0;
}
Double Cluster
Orion Nebula
Orion Nebula
One Parameter That Accepts Every String Type
Parameter passing is initialization, so the same permissive column applies to arguments. A single std::string_view parameter accepts a literal, a std::string, and another view, with no overloads and no copies:
#include <iostream>
#include <string>
#include <string_view>
void logObservation(std::string_view entry)
{
std::cout << "> " << entry << '\n';
}
int main()
{
logObservation("Albireo, gold and blue pair");
std::string owned { "Pleiades, six stars naked eye" };
logObservation(owned);
std::string_view borrowed { owned };
logObservation(borrowed);
return 0;
}
> Albireo, gold and blue pair
> Pleiades, six stars naked eye
> Pleiades, six stars naked eye
Compare that with a std::string parameter, which would accept the same three arguments but copy the characters on each of the three calls. One parameter type, three argument types, zero copies is the reason this is the default choice for read-only text.
Turning a View Back Into a std::string
Going the other way is where the compiler stops you. Passing a std::string_view to a std::string parameter is a compile error, not an automatic copy. The rule exists so that an expensive allocation can never sneak into your program without you writing something.
A `std::string_view` does not implicitly convert to a `std::string`. If you write a call that needs the conversion, the compiler rejects it rather than silently making a copy.
Two spellings ask for the copy explicitly. Either initialize a std::string from the view, or apply static_cast:
#include <iostream>
#include <string>
#include <string_view>
void archiveEntry(std::string entry)
{
std::cout << "archived: " << entry << '\n';
}
int main()
{
std::string_view observation { "Mizar splits cleanly at 60x" };
// archiveEntry(observation); // will not compile: no implicit conversion to std::string
std::string copied { observation };
archiveEntry(copied);
archiveEntry(static_cast<std::string>(observation));
return 0;
}
archived: Mizar splits cleanly at 60x
archived: Mizar splits cleanly at 60x
Uncommenting the third line of main() produces a conversion error naming std::string_view and std::string. Both of the lines below it compile, and both allocate and copy. That is not a bug to avoid; it is the cost being made visible at the point where you chose to pay it.
Reassigning a View Points It Somewhere Else
A view is read-only with respect to the characters, but the view variable itself is an ordinary object you can assign to. Assignment repoints the view. It never writes through to the text the view used to be reading:
#include <iostream>
#include <string>
#include <string_view>
int main()
{
std::string logbook { "Cassiopeia" };
std::string_view caption { logbook };
std::cout << caption << '\n';
caption = "Perseus";
std::cout << caption << '\n';
std::cout << logbook << '\n';
return 0;
}
Cassiopeia
Perseus
Cassiopeia
caption = "Perseus" aimed caption at the literal "Perseus". logbook still holds Cassiopeia, untouched. If you catch yourself expecting the last line to print Perseus, you are thinking of the view as a second name for the string. It is not; it is a separate object that happens to be looking at the same characters until you tell it to look elsewhere.
Literals That Are Already Views
By default a double-quoted literal in your source is a C-style string. Two suffixes change that, and both need their namespace brought in first:
#include <iostream>
#include <string>
#include <string_view>
int main()
{
using namespace std::string_literals;
using namespace std::string_view_literals;
std::cout << "raw C-style literal\n";
std::cout << "std::string literal\n"s;
std::cout << "std::string_view literal\n"sv;
return 0;
}
raw C-style literal
std::string literal
std::string_view literal
The sv suffix must be lower case, and it produces a std::string_view. Importing std::string_view_literals inside a function is one of the few places a using-directive is uncontroversial, since it pulls in literal suffixes and nothing else. Keep such directives out of headers and out of namespace scope.
You do not need the `sv` suffix to build a view. `std::string_view sv { "text" };` works perfectly well, because a C-style string literal converts implicitly. The suffix matters when you need an expression to already have view type, such as when a template or overload set must pick a type from the argument alone.
Named String Constants That Cost Nothing at Runtime
std::string allocates, and allocation is a runtime activity, which limits how far constexpr can go with it. std::string_view has no such limitation. A view of a literal is fully usable at compile time:
#include <iostream>
#include <string_view>
int main()
{
constexpr std::string_view telescope { "Newtonian 200mm f/5" };
constexpr std::string_view eyepiece { "Plossl 9mm" };
std::cout << telescope << " with " << eyepiece << '\n';
return 0;
}
Newtonian 200mm f/5 with Plossl 9mm
Reach for `constexpr std::string_view` when you need a named string constant. It gives you a readable name with no allocation and no runtime cost.
Key Terminology
std::string_view: A read-only handle onto characters owned by something else, declared in<string_view>- Read-only access: You may inspect and print the characters through the view, but not change them
- Implicit conversion: A conversion the compiler performs for you, as when a
std::stringargument binds to astd::string_viewparameter - Explicit conversion: A conversion you must write out, as when turning a view into a
std::stringby initialization orstatic_cast svsuffix: A literal suffix fromstd::string_view_literalsthat gives a double-quoted literal the typestd::string_view- Symbolic constant: A named constant standing in for a fixed value, which for text is best spelled
constexpr std::string_view
Looking Forward
Every example in this lesson viewed characters that clearly outlive the view: a string literal baked into the program, or a std::string declared in the same scope. That is the safe case, and it is the common case. The next lesson takes on the other case, where the characters go away or move while a view is still aimed at them, and shows what the compiler will and will not catch for you.
Summary
std::string_viewprovides read-only access to characters that already exist, without allocating or copying- The default read-only string parameter type is
std::string_view; usestd::stringonly when the function needs its own copy - A view can be initialized from a C-style string literal, a
std::string, or anotherstd::string_view, and all three conversions are implicit and free - Because parameter passing is initialization, one
std::string_viewparameter accepts all three argument types with no copies - The reverse conversion is blocked:
std::string_viewwill not implicitly become astd::string, so an expensive copy cannot slip in unnoticed - Ask for that copy explicitly by initializing a
std::stringfrom the view, or withstatic_cast<std::string>(view) - Assigning to a view repoints it at different characters and leaves the previously viewed string unchanged
- The lower-case
svsuffix builds astd::string_viewliteral, and needsusing namespace std::string_view_literalsin scope - Unlike
std::string,std::string_viewworks fully withconstexpr, which makesconstexpr std::string_viewthe right way to declare a named string constant
How did you find this lesson?
Your rating helps us improve the content.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Using std::string_view - Quiz
Test your understanding of the lesson.
Practice Exercises
Introduction to std::string_view
Practice using std::string_view for efficient string access.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!