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++17A 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 achar*.std::stringby 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:
| Alias | CharT | Since |
|---|---|---|
std::string_view | char | C++17 |
std::wstring_view | wchar_t | C++17 |
std::u16string_view | char16_t | C++17 |
std::u32string_view | char32_t | C++17 |
std::u8string_view | char8_t | C++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
#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
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
#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 lengthThis 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.
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 verifiedZero-allocation tokenization
#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 copiesHeterogeneous 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:
#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 compareWithout 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
// 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
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():
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++23No direct concatenation
string_view intentionally omits operator+ to prevent accidental allocations. Concatenation requires an explicit destination:
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++20See Also
std::stringβ owning counterpart; construct fromstring_viewwithstd::string{sv}std::spanβ analogous non-owning view for arbitrary element types (C++20)std::formatβ acceptsstring_viewnatively as a format argument (C++20)- Ranges library β
string_viewmodelscontiguous_range; works directly with all range algorithms