String and Span — std::string_view, std::span, String Operations
In this tutorial, you will learn about String and Span. We cover key concepts, practical examples, and best practices to help you master this topic.
C++17 std::string_view and C++20 std::span are non-owning views into character data and contiguous sequences, providing zero-copy access and rich read-only interfaces.
What You'll Learn
You will use std::string_view for efficient string parameter passing without copying, use std::span for non-owning views of arrays, vectors, and other contiguous sequences, perform common string operations (find, substr, concatenation), use std::string member functions effectively, and understand the conversion between string views and owning strings.
Why It Matters
Copying strings is expensive. In many codebases, a significant portion of time is spent allocating and copying std::string objects. string_view eliminates these copies for read-only access. Similarly, span eliminates the need to write separate overloads for C-style arrays and std::vector. These views are essential for writing efficient, generic C++.
Learning Path
graph LR
A["34: Stack, Queue, Priority Queue"] --> B["35: String & Span"]
B --> C["36: Algorithms Overview"]
C --> D["37: Sorting & Searching"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
std::string_view (C++17)
#include <iostream>
#include <string_view>
#include <string>
// Efficient parameter: works with std::string, const char*, string_view
void printLength(std::string_view sv) {
std::cout << "Length: " << sv.size() << "\n";
std::cout << "Data: " << sv << "\n";
}
int main() {
// No copies: sv points to existing data
printLength("Hello, World!"); // const char*
std::string s = "Hello from std::string";
printLength(s); // std::string
std::string_view sv = "Hello from view";
printLength(sv); // string_view
// Substring without allocation
std::string_view full = "Hello, World!";
std::string_view part = full.substr(7, 5); // "World", no allocation
std::cout << part << "\n";
// Find
auto pos = full.find("World");
if (pos != std::string_view::npos) {
std::cout << "Found at: " << pos << "\n";
}
// Remove prefix/suffix
std::string_view trim = " spaced text ";
trim.remove_prefix(3); // remove leading spaces
trim.remove_suffix(3); // remove trailing spaces
std::cout << "Trimmed: '" << trim << "'\n";
}
Important: string_view Does Not Own
std::string_view dangerous() {
std::string s = "temporary";
return s; // string_view now points to destroyed data!
}
int main() {
std::string_view sv = dangerous();
// std::cout << sv; // undefined behavior!
}
std::span (C++20)
#include <iostream>
#include <span>
#include <vector>
#include <array>
// Accepts any contiguous buffer: array, vector, C-array
void sum(std::span<const int> data) {
long total = 0;
for (int x : data) {
total += x;
}
std::cout << "Sum: " << total << "\n";
std::cout << "Size: " << data.size() << "\n";
}
void modify(std::span<int> data) {
for (int& x : data) {
x *= 2;
}
}
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
sum(vec); // works with vector
std::array<int, 3> arr = {10, 20, 30};
sum(arr); // works with array
int c_arr[] = {100, 200};
sum(c_arr); // works with C array
// Modify
modify(vec);
for (int x : vec) std::cout << x << " ";
std::cout << "\n"; // 2 4 6 8 10
// Subspan
std::span<int> full = vec;
std::span<int> first3 = full.first(3);
std::span<int> last2 = full.last(2);
std::span<int> middle = full.subspan(1, 3);
// Dynamic vs fixed extent
std::span<int> dynamic_span(vec); // dynamic extent
std::span<int, 3> fixed_span(arr.data(), 3); // fixed extent
}
String Operations
#include <iostream>
#include <string>
#include <sstream>
int main() {
// Construction
std::string s1 = "Hello";
std::string s2(5, 'a'); // "aaaaa"
std::string s3(s1, 0, 3); // "Hel"
// Concatenation
std::string result = s1 + " " + s2;
std::cout << result << "\n";
// Append
s1.append(" World");
s1 += "!";
// Insert
s1.insert(5, " there");
// Replace
s1.replace(6, 5, "everyone");
// Find
std::string text = "The quick brown fox";
size_t pos = text.find("brown");
if (pos != std::string::npos) {
std::cout << "brown at " << pos << "\n";
}
// rfind (reverse find)
pos = text.rfind('o');
// find_first_of, find_last_of, find_first_not_of
pos = text.find_first_of("aeiou");
// Substring
std::string sub = text.substr(4, 5); // "quick"
// Comparison
if (s1 == s2) {}
int cmp = s1.compare(s2);
// Numeric conversion
int num = std::stoi("42");
double d = std::stod("3.14");
std::string str = std::to_string(42);
// Stream-based
std::ostringstream oss;
oss << "Value: " << 42 << ", pi: " << 3.14;
std::string formatted = oss.str();
}
String Performance
#include <iostream>
#include <string>
int main() {
// Small String Optimization (SSO)
// Most implementations store small strings (<= 15 chars) on the stack
// This avoids heap allocation for short strings
std::string small = "hi"; // no heap allocation (SSO)
std::string large = "this is a very long string that exceeds SSO";
std::cout << "SSO capacity: " << small.capacity() << "\n";
// Reserve to avoid reallocation
std::string builder;
builder.reserve(1000);
for (int i = 0; i < 100; ++i) {
builder += "hello ";
}
}
Converting Between String and View
#include <iostream>
#include <string>
#include <string_view>
int main() {
std::string owner = "I own this data";
// string -> string_view (implicit)
std::string_view view = owner;
// string_view -> string (explicit)
std::string copy(view.data()); // copy
std::string copy2(view.begin(), view.end()); // copy
// View to C-string: NOT null-terminated by default!
// char buffer[256];
// std::strcpy(buffer, view.data()); // dangerous: view may not be null-terminated
// Safe: convert to std::string first
std::string safe(view);
const char* cstr = safe.c_str();
}
Common Mistakes
Mistake 1: Returning string_view from a Function Returning Local String
std::string_view getView() {
std::string local = "temp";
return local; // dangling view
}
Mistake 2: Assuming string_view is Null-Terminated
std::string_view sv = "hello";
printf("%s", sv.data()); // works (string literal is null-terminated)
// But substr is not:
std::string_view part = sv.substr(0, 2);
printf("%s", part.data()); // undefined behavior: not null-terminated
Mistake 3: Modifying Container Through span
std::span<int> sp = vec;
sp[0] = 99; // modifies vec[0] — be aware of side effects
Mistake 4: Passing string_view to Functions Expecting const char*
void legacy(const char* s);
std::string_view sv = "hello";
legacy(sv.data()); // may not be null-terminated
Create a temporary string: legacy(std::string(sv).c_str());
Mistake 5: Storing string_view as a Class Member
Unless you are certain the underlying data outlives the class, store std::string instead.
Mistake 6: Creating a span from a Temporary Container
std::span<const int> sp = getVector(); // dangling span
Practice Questions
- What is the advantage of
string_viewoverconst std::string&as a parameter? - When should you NOT use
string_view? - How does
std::spandiffer fromstd::vector? - What is the Small String Optimization (SSO)?
- Write a function that tokenizes a
string_viewby a delimiter without allocations.
Challenge
Implement a split function that takes a string_view and a delimiter character, and returns a vector of string_view substrings (no allocations for the substrings themselves). Compare performance with a version that returns std::vector<std::string>.
FAQ
Mini Project
Build a CSV parser using string_view:
#include <iostream>
#include <string_view>
#include <vector>
#include <sstream>
class CSVRow {
private:
std::vector<std::string_view> fields_;
public:
CSVRow(std::string_view line) {
size_t start = 0;
for (size_t i = 0; i <= line.size(); ++i) {
if (i == line.size() || line[i] == ',') {
if (i > start) {
fields_.push_back(line.substr(start, i - start));
} else {
fields_.push_back({});
}
start = i + 1;
}
}
}
size_t size() const { return fields_.size(); }
std::string_view get(size_t index) const {
return index < fields_.size() ? fields_[index] : std::string_view{};
}
void print() const {
for (size_t i = 0; i < fields_.size(); ++i) {
if (i > 0) std::cout << " | ";
std::cout << fields_[i];
}
std::cout << "\n";
}
};
int main() {
std::string csv = "Name,Age,City\nAlice,30,New York\nBob,25,Los Angeles";
std::istringstream stream(csv);
std::string line;
bool first = true;
while (std::getline(stream, line)) {
if (first) {
std::cout << "Header: ";
first = false;
} else {
std::cout << "Row: ";
}
CSVRow row(line);
row.print();
}
}
What's Next
Views avoid copying and improve performance. The next lesson begins Module 5 on STL Algorithms, starting with an overview of algorithm categories, Iterator requirements, and the ranges library.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro