C++ Programming
String Manipulation
Coding Tutorials
Programming Tips
C++ Strings

How to trim a stdstring?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

std::string does not have a built-in trim() method. To trim whitespace from a std::string, write small helper functions using std::find_if with std::isspace, or use find_first_not_of and find_last_not_of with a whitespace character set. The standard approach takes about five lines of code and covers leading whitespace (left trim), trailing whitespace (right trim), or both.

cpp
1#include <algorithm>
2#include <cctype>
3#include <string>
4
5// Trim from the left (in place)
6void ltrim(std::string& s) {
7    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
8        [](unsigned char ch) { return !std::isspace(ch); }));
9}
10
11// Trim from the right (in place)
12void rtrim(std::string& s) {
13    s.erase(std::find_if(s.rbegin(), s.rend(),
14        [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end());
15}
16
17// Trim both ends (in place)
18void trim(std::string& s) {
19    ltrim(s);
20    rtrim(s);
21}

How the Algorithm Works

Left Trim

ltrim uses std::find_if with a forward iterator to locate the first non-whitespace character. It then erases everything from the beginning of the string up to that position:

cpp
1// Before: "   hello"
2//          ^  ^--- find_if stops here (first non-space)
3//          |------ erase from here to the found position
4// After:  "hello"

Right Trim

rtrim uses reverse iterators (rbegin, rend) to scan from the end of the string backward. It finds the last non-whitespace character, converts the reverse iterator to a forward iterator with .base(), and erases from that point to the end:

cpp
1// Before: "hello   \n"
2//               ^-------- find_if (reverse) stops here
3//               .base()--> erase from here to end
4// After:  "hello"

Combined Trim

trim calls ltrim first, then rtrim. Order does not matter, but trimming the left first means rtrim operates on a slightly shorter string.

Returning a Copy Instead of Mutating

Sometimes you want to preserve the original string. Return a copy by taking the string parameter by value:

cpp
1#include <algorithm>
2#include <cctype>
3#include <string>
4
5std::string trim_copy(std::string s) {
6    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
7        [](unsigned char ch) { return !std::isspace(ch); }));
8    s.erase(std::find_if(s.rbegin(), s.rend(),
9        [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end());
10    return s;
11}

Usage:

cpp
1std::string original = "  data  ";
2std::string cleaned = trim_copy(original);
3// original is still "  data  "
4// cleaned is "data"

This version is convenient in function chains, logging, and anywhere mutation would be unexpected.

Alternative: Using find_first_not_of and find_last_not_of

An alternative approach uses std::string's member functions to find the first and last characters that are not in a given set:

cpp
1#include <string>
2
3std::string trim_v2(const std::string& s) {
4    const std::string whitespace = " \t\n\r\f\v";
5    auto start = s.find_first_not_of(whitespace);
6    if (start == std::string::npos) {
7        return "";
8    }
9    auto end = s.find_last_not_of(whitespace);
10    return s.substr(start, end - start + 1);
11}

This approach is arguably more readable because the whitespace character set is explicit and easy to modify. It also avoids the reverse-iterator-to-forward-iterator conversion that confuses some readers.

Trimming Specific Characters

The same find_first_not_of and find_last_not_of pattern works for trimming any set of characters:

cpp
1std::string trim_chars(const std::string& s, const std::string& chars) {
2    auto start = s.find_first_not_of(chars);
3    if (start == std::string::npos) {
4        return "";
5    }
6    auto end = s.find_last_not_of(chars);
7    return s.substr(start, end - start + 1);
8}

Examples:

cpp
1// Remove surrounding quotes
2std::string a = trim_chars("\"hello\"", "\"");
3// a == "hello"
4
5// Remove leading/trailing slashes
6std::string b = trim_chars("/api/v1/users/", "/");
7// b == "api/v1/users"
8
9// Remove multiple character types
10std::string c = trim_chars("##--title--##", "#-");
11// c == "title"

C++20 and C++23 Improvements

C++20 introduced std::ranges, which can make the trim slightly more expressive:

cpp
1#include <algorithm>
2#include <cctype>
3#include <ranges>
4#include <string>
5
6std::string trim_ranges(std::string s) {
7    auto not_space = [](unsigned char ch) { return !std::isspace(ch); };
8
9    // Drop leading whitespace
10    auto left = std::ranges::find_if(s, not_space);
11    s.erase(s.begin(), left);
12
13    // Drop trailing whitespace
14    auto right = std::ranges::find_if(s | std::views::reverse, not_space);
15    s.erase(right.base(), s.end());
16
17    return s;
18}

C++23 does not add a dedicated trim() method to std::string, so helper functions remain necessary.

Using std::string_view for Read-Only Trimming

If you only need to read the trimmed result without allocating a new string, std::string_view avoids the copy:

cpp
1#include <string_view>
2#include <cctype>
3
4std::string_view trim_view(std::string_view sv) {
5    while (!sv.empty() && std::isspace(static_cast<unsigned char>(sv.front()))) {
6        sv.remove_prefix(1);
7    }
8    while (!sv.empty() && std::isspace(static_cast<unsigned char>(sv.back()))) {
9        sv.remove_suffix(1);
10    }
11    return sv;
12}

This is zero-allocation and works well in parsers that process large amounts of text. The returned string_view is only valid as long as the original string exists.

Comparison of Approaches

ApproachMutates originalAllocatesCustom charsReadability
std::find_if + erase (in place)YesNoRequires lambda changeModerate
std::find_if + erase (copy)NoYesRequires lambda changeModerate
find_first_not_of + substrNoYesPass as string parameterHigh
std::string_view trimNoNoRequires manual loopModerate
Boost boost::trimYesNoVia boost::trim_ifHigh

The unsigned char Detail

In every example that uses std::isspace, the lambda parameter is unsigned char, not char. This is intentional. The C standard specifies that character classification functions like isspace accept unsigned char or EOF. Passing a signed char value greater than 127 (common in UTF-8 text) is undefined behavior:

cpp
1// Dangerous: char may be negative on some platforms
2[](char ch) { return !std::isspace(ch); }      // UB for non-ASCII
3
4// Correct: cast to unsigned char
5[](unsigned char ch) { return !std::isspace(ch); } // well-defined

This detail rarely causes visible bugs in ASCII-only codebases, which is why it gets overlooked. But it matters the moment your program processes text with accented characters, CJK text, or any other non-ASCII content.

Boost Alternative

If your project already uses Boost, the Boost.StringAlgo library provides ready-made trim functions:

cpp
1#include <boost/algorithm/string/trim.hpp>
2
3std::string s = "  hello  ";
4
5boost::trim(s);            // in-place trim
6auto copy = boost::trim_copy(s); // returns trimmed copy
7boost::trim_left(s);       // trim left only
8boost::trim_right(s);      // trim right only
9
10// Trim specific characters
11boost::trim_if(s, boost::is_any_of("/-"));

Boost trim functions handle the unsigned char issue internally, so you do not need to worry about it.

Common Pitfalls

Forgetting to handle strings that are entirely whitespace causes find_first_not_of to return std::string::npos. If that return value is passed to substr without checking, the result is usually a garbage string or an exception. Always check for npos before computing the substring.

Using char instead of unsigned char in the isspace lambda is undefined behavior for non-ASCII input. The code may appear to work during testing with English text and then crash or misbehave with internationalized content.

Mixing in-place and copy-returning trim functions in the same codebase without clear naming conventions causes confusion. Establish a naming rule like trim() for in-place and trim_copy() for copy-returning, and apply it consistently.

Assuming std::isspace handles Unicode whitespace is incorrect. std::string stores bytes, and std::isspace only recognizes ASCII whitespace characters (space, tab, newline, carriage return, form feed, vertical tab). Unicode whitespace characters like the non-breaking space (U+00A0) or ideographic space (U+3000) require a Unicode-aware library such as ICU.

Creating trim helpers that are only used in one file but duplicated across the project is a maintenance issue. Put the helpers in a shared utility header so every module uses the same implementation.

Summary

  • std::string has no built-in trim(). Write small helpers using std::find_if with std::isspace or find_first_not_of / find_last_not_of.
  • Choose between in-place mutation and copy-returning based on your use case, and name the functions to make the behavior obvious.
  • Use find_first_not_of / find_last_not_of when you need to trim a custom set of characters.
  • Always use unsigned char in std::isspace lambdas to avoid undefined behavior with non-ASCII input.
  • Check for std::string::npos when the input string might be entirely whitespace.
  • Consider std::string_view for zero-allocation trimming in performance-sensitive parsers.
  • Use Boost trim if your project already depends on Boost.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.