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.

Best Practice
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.

Warning
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.

Key Concept
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
Best Practice
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::string argument binds to a std::string_view parameter
  • Explicit conversion: A conversion you must write out, as when turning a view into a std::string by initialization or static_cast
  • sv suffix: A literal suffix from std::string_view_literals that gives a double-quoted literal the type std::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_view provides read-only access to characters that already exist, without allocating or copying
  • The default read-only string parameter type is std::string_view; use std::string only when the function needs its own copy
  • A view can be initialized from a C-style string literal, a std::string, or another std::string_view, and all three conversions are implicit and free
  • Because parameter passing is initialization, one std::string_view parameter accepts all three argument types with no copies
  • The reverse conversion is blocked: std::string_view will not implicitly become a std::string, so an expensive copy cannot slip in unnoticed
  • Ask for that copy explicitly by initializing a std::string from the view, or with static_cast<std::string>(view)
  • Assigning to a view repoints it at different characters and leaves the previously viewed string unchanged
  • The lower-case sv suffix builds a std::string_view literal, and needs using namespace std::string_view_literals in scope
  • Unlike std::string, std::string_view works fully with constexpr, which makes constexpr std::string_view the right way to declare a named string constant