Skip to content
C++
Library
since C++17
Basic

std::string_view

"Non-owning, read-only string reference (C++17): zero-copy substrings, universal parameter type, constexpr usage, and lifetime hazards."

std::string_viewsince C++17

A non-owning, read-only view into a contiguous character sequence β€” stored as a (pointer, length) pair β€” that provides the full string inspection API without any allocation or copying.

Overview

Before C++17, functions accepting read-only strings faced an unavoidable trade-off:

  • const char* β€” no allocation, but loses length information and offers no member functions.
  • const std::string& β€” full API, but forces a heap allocation when the caller passes a string literal or a char*.
  • std::string by value β€” always copies, always allocates.

std::string_view resolves this trilemma. It is a lightweight pair (typically 16 bytes on 64-bit platforms) of a pointer and a length. It provides almost the entire std::string read-only interface, accepts any contiguous character source implicitly, and imposes zero allocation overhead regardless of the call site.

The full class template is std::basic_string_view<CharT, Traits>. The standard provides these aliases:

AliasCharTSince
std::string_viewcharC++17
std::wstring_viewwchar_tC++17
std::u16string_viewchar16_tC++17
std::u32string_viewchar32_tC++17
std::u8string_viewchar8_tC++20

Every constructor and member function (except copy()) is constexpr since C++17, making string_view the natural currency for compile-time string processing. It satisfies contiguous_range and integrates directly with all <algorithm> and <ranges> machinery.

Syntax

Construction

cpp
#include <string_view>

// From string literal β€” zero overhead, constexpr               C++17
constexpr std::string_view sv1 = "hello world";
constexpr std::string_view sv2{"hello", 5};   // explicit length, no null-term needed

// Literal suffix: produces string_view directly                C++17
using namespace std::string_view_literals;
constexpr auto sv3 = "hello"sv;   // type is string_view, not const char*

// From std::string β€” implicit conversion via operator string_view()
std::string s = "example";
std::string_view sv4 = s;          // borrows s's buffer; s must outlive sv4

// From arbitrary char buffer β€” safe for non-null-terminated data
const char* buf = get_network_packet();
std::size_t len = packet_length();
std::string_view sv5{buf, len};    // does NOT assume '\0' at buf[len]

Core member functions

cpp
std::string_view sv = "Hello, World!";

// Capacity
sv.size();      // 13                        constexpr C++17
sv.empty();     // false

// Access β€” unchecked and checked
sv[0];          // 'H'    β€” no bounds check
sv.at(0);       // 'H'    β€” throws std::out_of_range
sv.front();     // 'H'
sv.back();      // '!'
sv.data();      // const char* β€” NOT null-terminated in general!

// Zero-copy subranges β€” O(1) pointer arithmetic
sv.substr(7, 5);       // "World" β€” new view, no copy
sv.remove_prefix(7);   // sv is now "World!" β€” modifies the view object, not the data
sv.remove_suffix(1);   // sv is now "World"

// Search β€” identical signatures to std::string
sv.find("World");              // 7
sv.find_first_of("aeiou");    // 1 ('e')
sv.rfind('o');                 // 8

// Prefix / suffix tests                     C++20
sv.starts_with("Hello");   // true
sv.ends_with("!");          // true

// Substring containment test                C++23
sv.contains("World");      // true

// Comparison β€” lexicographic, constexpr
sv == "Hello, World!";
sv.compare("hello");       // < 0 (case-sensitive, 'H' < 'h')

Examples

Universal read-only string parameter

cpp
#include <string_view>
#include <print>    // C++23

void log(std::string_view message) {       // C++17 β€” accepts everything, copies nothing
    std::println("[LOG] {}", message);     // std::format accepts string_view natively C++20
}

std::string owned{"from std::string"};
log(owned);                               // no copy β€” view into owned's buffer
log("string literal");                    // no allocation β€” view into rodata
log("hello"sv);                           // string_view literal directly  C++17
log({buf, buf_len});                      // raw buffer with explicit length

This is strictly superior to const std::string& for read-only parameters: it accepts the same types, plus non-null-terminated buffers, at lower cost.

constexpr enum-to-string

string_view's full constexpr support makes it ideal for compile-time string mapping. Returning a view of a string literal is safe because literals have static storage duration.

cpp
enum class Status { Unknown, Created, Connected };

[[nodiscard]] constexpr std::string_view to_string(Status s) noexcept {  // C++17
    switch (s) {
        case Status::Unknown:   return "Unknown";
        case Status::Created:   return "Created";
        case Status::Connected: return "Connected";
    }
    return "<invalid>";
}

static_assert(to_string(Status::Created) == "Created");  // compile-time verified

Zero-allocation tokenization

cpp
#include <string_view>
#include <vector>

// All returned views borrow from the original buffer β€” zero heap allocations
std::vector<std::string_view> split(std::string_view str, char delim) {
    std::vector<std::string_view> tokens;
    std::size_t start = 0, end;
    while ((end = str.find(delim, start)) != std::string_view::npos) {
        tokens.push_back(str.substr(start, end - start));
        start = end + 1;
    }
    tokens.push_back(str.substr(start));
    return tokens;
}

// The caller must keep 'csv' alive for as long as 'fields' is used:
std::string csv = "alpha,beta,gamma";
auto fields = split(csv, ',');   // three views into csv β€” no copies

Heterogeneous map lookup

The C++14 transparent comparator std::less<> allows std::map and std::set to search with a string_view key without constructing a temporary std::string:

cpp
#include <map>
#include <string>
#include <string_view>

std::map<std::string, int, std::less<>> registry;  // note: std::less<>, not std::less<std::string>
registry["alpha"] = 1;
registry["beta"]  = 2;

std::string_view key = "alpha";
auto it = registry.find(key);   // O(log n), zero allocation   C++14 transparent compare

Without std::less<>, even find(string_view) would silently construct a temporary std::string.

Best Practices

Use string_view for all read-only string parameters. It is a strict improvement over const std::string& in almost every case. The only exception: if you immediately need a std::string inside the function (and you cannot avoid the copy), taking std::string by value is better so the compiler can elide the copy at the call site.

Use the sv literal suffix for constants. "hello"sv is resolved at compile time to a string_view with no runtime construction; it also documents the intent to readers.

Return string_view only from stable storage. Safe sources: string literals (static duration), data members of a longer-lived object, or caller-provided buffers. Functions that build or transform strings must return std::string.

Leverage the constexpr interface. String searches, comparisons, and substrings are all constexpr since C++17. Use string_view in if constexpr branches and static_assert expressions where you previously had to rely on const char* tricks.

Remember std::hash is specialized. std::unordered_map<std::string, V> does not support heterogeneous lookup by default. If you need hash-based lookup with string_view keys, use a custom hasher or wait for C++26 std::unordered_map heterogeneous support.

Common Pitfalls

Dangling view from a temporary

cpp
// WRONG β€” concatenation produces a temporary std::string that dies immediately
std::string_view sv = first + " " + last;   // UB: sv points to freed memory

// WRONG β€” returned local
std::string_view bad_return() {
    std::string s = build_string();
    return s;   // UB: s is destroyed at the closing brace
}

// CORRECT β€” return std::string; NRVO eliminates the copy
std::string good_return() {
    return build_string();
}

Views into reallocated or moved strings

cpp
std::string source = "alpha,beta,gamma";
auto tokens = split(source, ',');    // tokens hold views into source's buffer

source.reserve(1024);   // may reallocate β€” all tokens now dangle!
source = "different";   // definitely reallocates β€” all tokens now dangle!

Treat the backing string as immutable and alive for the entire lifetime of any derived views.

Passing to null-terminated C APIs

string_view::data() is not guaranteed to be null-terminated, especially after substr() or remove_prefix():

cpp
std::string_view sv = "hello world";
std::string_view partial = sv.substr(0, 5);   // "hello" β€” no '\0' at position 5

// WRONG β€” UB if the next byte in memory is not '\0'
printf("%s", partial.data());

// CORRECT β€” explicit conversion or length-aware write
printf("%s", std::string(partial).c_str());
fwrite(partial.data(), 1, partial.size(), stdout);
std::println("{}", partial);   // std::format / print handle this correctly   C++23

No direct concatenation

string_view intentionally omits operator+ to prevent accidental allocations. Concatenation requires an explicit destination:

cpp
std::string_view a = "hello", b = " world";

// auto s = a + b;                         // compile error

auto s  = std::string{a} + std::string{b};  // two views β†’ one owned string
auto s2 = std::format("{}{}", a, b);         // via format                   C++20

See Also

  • std::string β€” owning counterpart; construct from string_view with std::string{sv}
  • std::span β€” analogous non-owning view for arbitrary element types (C++20)
  • std::format β€” accepts string_view natively as a format argument (C++20)
  • Ranges library β€” string_view models contiguous_range; works directly with all range algorithms