Skip to content

String and Span — std::string_view, std::span, String Operations

DodaTech Updated 2026-06-28 7 min read

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

  1. What is the advantage of string_view over const std::string& as a parameter?
  2. When should you NOT use string_view?
  3. How does std::span differ from std::vector?
  4. What is the Small String Optimization (SSO)?
  5. Write a function that tokenizes a string_view by 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

Does `string_view` own the data?

No. It is a non-owning view. The underlying data must outlive the view. Use std::string when you need ownership.

What is the difference between `span` and `string_view`?

span is a view over any contiguous sequence (int[], vector, array). string_view is specifically for character data and provides string-specific operations (find, substr).

Can I modify data through `string_view`?

No. string_view provides const access only. Use span with a non-const type parameter if you need to modify.

Is `string_view` always null-terminated?

No. Only the original C++ string literals are null-terminated. Substrings and views into std::string middle are not null-terminated.

What is the size of a `span`?

span with dynamic extent stores a pointer and a size (two words). span with fixed extent stores only a pointer.

Can I use `span` as a function parameter for arrays?

Yes! This is the primary use case. Instead of void f(int* arr, size_t n), write void f(std::span<int> arr).

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