How to trim a std::string?
Asked Answered
D

51

1011

I'm currently using the following code to right-trim all the std::strings in my programs:

std::string s;
s.erase(s.find_last_not_of(" \n\r\t")+1);

It works fine, but I wonder if there are some edge-cases where it might fail?

Of course, answers with elegant alternatives and also left-trim solution are welcome.

Diarrhoea answered 19/10, 2008 at 19:23 Comment(0)
G
864

New answer for C++11

#include <algorithm> 
#include <cctype>
#include <locale>

// trim from start (in place)
inline void ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
        return !std::isspace(ch);
    }));
}

// trim from end (in place)
inline void rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
        return !std::isspace(ch);
    }).base(), s.end());
}

Thanks to https://mcmap.net/q/54336/-std-ptr_fun-replacement-for-c-17 for bringing up the modern solution.

Convenience functions

// trim from both ends (in place)
inline void trim(std::string &s) {
    rtrim(s);
    ltrim(s);
}

// trim from start (copying)
inline std::string ltrim_copy(std::string s) {
    ltrim(s);
    return s;
}

// trim from end (copying)
inline std::string rtrim_copy(std::string s) {
    rtrim(s);
    return s;
}

// trim from both ends (copying)
inline std::string trim_copy(std::string s) {
    trim(s);
    return s;
}

Updated answer for C++03

To address some comments about accepting a parameter by reference, modifying and returning it. I Agree. An implementation that I would likely prefer would be two sets of functions, one for in place and one which makes a copy. A better set of examples would be:

#include <algorithm> 
#include <functional> 
#include <cctype>
#include <locale>

// trim from start (in place)
inline void ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
            std::not1(std::ptr_fun<int, int>(std::isspace))));
}

// trim from end (in place)
inline void rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(),
            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
}

// the remaining functions (trim() et al.) are identical to the new C++11 version

std::ptr_fun is needed to disambiguate std::isspace because there is a second definition which supports locales. This could have been a cast just the same, but I tend to like this better.

Original answer for C++03

I am keeping this original answer for context and in the interest of keeping the high voted answer still available.

#include <algorithm> 
#include <functional> 
#include <cctype>
#include <locale>

// trim from start
inline std::string &ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
            std::not1(std::ptr_fun<int, int>(std::isspace))));
    return s;
}

// trim from end
inline std::string &rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(),
            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
    return s;
}

// trim from both ends
inline std::string &trim(std::string &s) {
    return ltrim(rtrim(s));
}
Gulick answered 20/10, 2008 at 5:46 Comment(25)
This code was failing on some international strings (shift-jis in my case, stored in a std::string); I ended up using boost::trim to solve the problem.Firstrate
I'd use pointers instead of references, so that from the callpoint is much easier to understand that these functions edit the string in place, instead of creating a copy.Inconsonant
Note that with isspace you can easily get undefined behaviour with non-ASCII characters stacked-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69fAdey
@R.MartinhoFernandes, true, a solution that I've used in the past is this template: template<int (&F)(int)> int safe_ctype(unsigned char c) { return F(c); } which can be used like this: safe_ctype<std::isspace>(ch);Gulick
Why the static? Is this where an anonymous namespace would be preferred?Affair
@TrevorHickey, sure, you could use an anonymous namespace instead if you prefer.Gulick
If this isn't compiling for you, or you subscribe to the idea that "a line of code is not an abstraction", see my gist implementation, which implements these functions using only iteratorsByrn
For naming consistency with std::string I'd recommend the names trim_front and trim_backGodinez
One other suggestion would be to make this generic and support different forms of std::basic_string. <br /> template<class Ch, class Tr, class Al> inline void ltrim(std::basic_string<Ch, Tr, Al>& s) {...}Autotoxin
See my answer below to support a wider range of characters to be trimmed.Uphemia
Returning and modifying the argument: Bad! :-)Kidron
@JohannesOvermann, I generally agree. The intention was to make it simple to combine this function with other calls. If I were to re-write it today. I would make a trim (which does the work in place) and a trimmed which would would return a copy of the string trimmed.Gulick
@EvanTeran AFAICT the idiomatic modern C++ way would be to return by value, not modify a reference. Anyway, Johannes' issue was that you were both accepting a non-const reference argument and modifying it in-place, and returning the same reference to the same argument. What's the purpose of that? It seems to me that it would end up being called in situations where it looks like a new string is returned and, hence, like the input string isn't being modified - but it was all along - which seems like a recipe for confusion and/or disaster.Kidder
@Kidder look carefully, the current version of this post has two versions: the ones that take by reference modify in place and return nothing. The ones that end in "ed" (the copying ones). Take a parameter by copy, modify that, and return it by value. Due to c++11 move semantics, the "copying" versions will perform the absolute minimum number of copies. Regarding "what's the purpose of that", I already answered that in my previous comment. Essentially it was "call chaining".Gulick
in rtime what is the purpose of base call to find_if??Nombril
With C++17 it will probably be better to use std::string_viewDorcasdorcea
@Dorcasdorcea I agree. I just made my version and it runs 4 times faster on 1000000 strings than the accepted answer. I use a manual loop and a custom is_space function which uses a switch case.Woodcutter
@tom Replace int ch with unsigned char ch in closure and it won't trigger assertion failure.Conjecture
Note that depending on the implementation, right-trimming a string before left-trimming might be faster than the other way around.Acoustician
@Tom: Shift-JIS is not "international" if you live in a country that uses it. You mean non-ASCII.Polysyllabic
The behaviour of passing "int ch" to "isspace" is undefined replace "int ch" with "unsigned char ch". [](unsigned char ch) { return !std::isspace(ch); } see: en.cppreference.com/w/cpp/string/byte/isspaceDixil
why rtrim does not need a base()++? seems like rtrim is erasing a last non-white space characterStomatal
@Stomatal Nah, it doesn't need that. Testing it shows that it operates correctly: godbolt.org/z/6eG6vdWTPGulick
These funciton will crash when passed in a null char*. For example: trim((char*)(0)). Hope this be fixed. @EvanTeranChemisette
Constructing a std::string from a null pointer is in itself a bug and is not allowed by the C++ standard. The fix is to not pass a null pointer to these functions.Gulick
P
465

Using Boost's string algorithms would be easiest:

#include <boost/algorithm/string.hpp>

std::string str("hello world! ");
boost::trim_right(str);

str is now "hello world!". There's also trim_left and trim, which trims both sides.


If you add _copy suffix to any of above function names e.g. trim_copy, the function will return a trimmed copy of the string instead of modifying it through a reference.

If you add _if suffix to any of above function names e.g. trim_copy_if, you can trim all characters satisfying your custom predicate, as opposed to just whitespaces.

Pelagian answered 19/10, 2008 at 19:55 Comment(7)
What does boost use to determine if a character is whitespace?Firstrate
It depends on the locale. My default locale (VS2005, en) means tabs, spaces, carriage returns, newlines, vertical tabs and form feeds are trimmed.Heedful
I'm already using lots of boost, #include <boost/format.hpp> #include <boost/tokenizer.hpp> #include <boost/lexical_cast.hpp> but was worried about code bloat for adding in <boost/algorithm/string.hpp> when there are already std::string::erase based alternatives. Happy to report when comparing MinSizeRel builds before and after adding it, that boost's trim didn't increase my codesize at all (must already be paying for it somewhere) and my code isn't cluttered with a few more functions.Hypervitaminosis
@MattyT: What reference are you using for this list (determining if a character is whitespace)?Demetria
A few very quick tests on my particular machine with my particular string tell me that boost is taking just over twice as long to trim it than Evan's solution above.Paleobotany
does not really answer the question which asks for std::string (not for boost or any other library ...)Fancie
I'd suggest adding an example for trim_copy_if ... but the edit queue is full. Here it is: std::cout << "[" << boost::trim_copy_if(std::string("<hello >,|"), boost::is_any_of(" \n\t,<>|")) << "]\n";, from boost's trim_example.cppBackstairs
H
105

What you are doing is fine and robust. I have used the same method for a long time and I have yet to find a faster method:

const char* ws = " \t\n\r\f\v";

// trim from end of string (right)
inline std::string& rtrim(std::string& s, const char* t = ws)
{
    s.erase(s.find_last_not_of(t) + 1);
    return s;
}

// trim from beginning of string (left)
inline std::string& ltrim(std::string& s, const char* t = ws)
{
    s.erase(0, s.find_first_not_of(t));
    return s;
}

// trim from both ends of string (right then left)
inline std::string& trim(std::string& s, const char* t = ws)
{
    return ltrim(rtrim(s, t), t);
}

By supplying the characters to be trimmed you have the flexibility to trim non-whitespace characters and the efficiency to trim only the characters you want trimmed.

Homely answered 19/8, 2014 at 14:16 Comment(2)
if you use basic_string and template on the CharT you can do this for all strings, just use a template variable for the whitespace so that you use it like ws<CharT>. technically at that point you could make it ready for c++20 and mark it constexpr too as this implies inlineHyposthenia
@Hyposthenia Indeed. A bit complicated to put in an answer here though. I have written template functions for this and it is certainly quite involved. I have tried a bunch of different approaches and still not sure which is the best.Homely
L
73

Try this, it works for me.

inline std::string trim(std::string& str)
{
    str.erase(str.find_last_not_of(' ')+1);         //suffixing spaces
    str.erase(0, str.find_first_not_of(' '));       //prefixing spaces
    return str;
}
Lightweight answered 28/6, 2011 at 0:47 Comment(10)
@rgove Please explain. str.find_last_not_of(x) returns the position of the first character not equal to x. It only returns npos if no chars do not match x. In the example, if there are no suffixing spaces, it will return the equivalent of str.length() - 1, yielding essentially str.erase((str.length() - 1) + 1). That is, unless I am terribly mistaken.Enschede
This fails for all-whitespace strings. (Adding an if not empty before the second 'erase()' fixes this.) Also return the modified value and modifying the argument passed by reference is probably misleading when reading code using the return value.Kidron
@JohannesOvermann: Why should it fail for all-whitespace strings?Gamba
@robert: Argh, yes, ok. It works because npos+1 == 0 in practice. But I do not really like arithmetic with npos. :-)Kidron
This should return std::string& to avoid unnecessarily invoking the copy constructor.Infallibilism
I am confused why this returns a copy after modifying the return parameter?Homely
@Homely So you can trim and assign or return the result at the same time, e.g. std::string myString(trim(myOtherString)) or return Trim(myString). Common practice.Decrepit
@Decrepit My confusion is why return a copy instead of a reference. It makes more sense to me to return std::string&.Homely
If you change the order (make it first to remove suffixing spaces then prefixing spaces) it will be more efficient.Pasteboard
@mhsmith, I don't know why there are this many up-votes for your comment, but if you look at it carefully, that is not the case. If the string contains no suffixing spaces, str.find_last_not_of(' ') will return the index of the last character in the string. Thus the erase will attempt to delete from the end of the string, and nothing will be deleted, which is exactly the expected action.Potentiality
M
67

Use the following code to right trim (trailing) spaces and tab characters from std::strings (ideone):

// trim trailing spaces
size_t endpos = str.find_last_not_of(" \t");
size_t startpos = str.find_first_not_of(" \t");
if( std::string::npos != endpos )
{
    str = str.substr( 0, endpos+1 );
    str = str.substr( startpos );
}
else {
    str.erase(std::remove(std::begin(str), std::end(str), ' '), std::end(str));
}

And just to balance things out, I'll include the left trim code too (ideone):

// trim leading spaces
size_t startpos = str.find_first_not_of(" \t");
if( string::npos != startpos )
{
    str = str.substr( startpos );
}
Magnification answered 7/12, 2008 at 19:45 Comment(12)
This won't detect other forms of whitespace... newline, line feed, carriage return in particular.Firstrate
Right. You have to customize it for the whitespace you're looking to trim. My particular application was only expecting spaces and tabs, but you can add \n\r to catch the others.Magnification
str.substr(...).swap(str) is better. Save an assignment.Tobietobin
@Tobietobin Won't it use move assignment basic_string& operator= (basic_string&& str) noexcept; ?Dramaturgy
This answer does not alter strings that are ALL spaces. Which is a fail.Flight
^ before making comments , make sure you're trying the answer posted. @Firstrate to include all those , make sure you're including them inside first/last_not_of function calls.Grefe
@TomAndersen , check for condition where string::npos != startpos fails , meaning an else statement to above ifs. Inside it trim all whitespace using erase-remove idiom. Here's an example : ideone.com/eelaVOGrefe
@BilltheLizard you can include else statement in the answer so that ot might benifit some others.Grefe
For your right-trimming... why not a simple resize()? It'll probably just involve a single integer decrement; doesn't get much cheaper than that...Polysyllabic
This answer fails if the string only contains spaces.Homely
Thank you, @AbhinavGauniyal. I finally got around to editing your code into my answer.Magnification
Instead of " \t", I'd do " \t\n"Situated
C
61

Bit late to the party, but never mind. Now C++11 is here, we have lambdas and auto variables. So my version, which also handles all-whitespace and empty strings, is:

#include <cctype>
#include <string>
#include <algorithm>

inline std::string trim(const std::string &s)
{
   auto wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
   auto wsback=std::find_if_not(s.rbegin(),s.rend(),[](int c){return std::isspace(c);}).base();
   return (wsback<=wsfront ? std::string() : std::string(wsfront,wsback));
}

We could make a reverse iterator from wsfront and use that as the termination condition in the second find_if_not but that's only useful in the case of an all-whitespace string, and gcc 4.8 at least isn't smart enough to infer the type of the reverse iterator (std::string::const_reverse_iterator) with auto. I don't know how expensive constructing a reverse iterator is, so YMMV here. With this alteration, the code looks like this:

inline std::string trim(const std::string &s)
{
   auto  wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
   return std::string(wsfront,std::find_if_not(s.rbegin(),std::string::const_reverse_iterator(wsfront),[](int c){return std::isspace(c);}).base());
}
Childers answered 31/7, 2013 at 17:3 Comment(10)
I always want one function call to trim string, instead of implementing itAhmad
For what it's worth, there's no need to use that lambda. You can just pass std::isspace: auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);Halfon
@Halfon compilers aren't necessarily that smart. doing what you say is ambiguous: candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)Gathers
@Gathers It works in visual studio (cl 19), but not on clang (3.9.1) or gcc (6.3).Kummerbund
Also I notice that the number of assembly lines for this answer (1st function) is ~1100 lines while for example this answer is < 100 lines. Haven't investigated further than that though.Kummerbund
I'm wondering what't the meaning of .base() ? could you tell me, pleaseAnglim
This is an RTFM, really. std::reverse_iterator::base returns the underlying iterator that a particular reverse_iterator points to. Reverse iterators always point one past their base iterator (i.e. an offset of -1) so base() is used to fix up the referenced element. Effectively rev_iter.base() - 1 == rev_iter.Childers
@Halfon No, you can't. isspace has two overloads. Moreover, taking the address of a function in the standard library is UB since C++20.Hermie
@L.F. Aw, come on. I wrote that answer 5 years ago. There better be a compiler warning for this. What is the second overload? Informal docs leave me hanging: en.cppreference.com/w/cpp/string/byte/isspaceHalfon
@Halfon the other overload is the one taking a locale. ::isspace would do before C++20 (provided you include the C header), though. Actually, an additional problem is that the argument should be cast to unsigned char before being fed to isspace, but that’s another story.Hermie
T
31

http://ideone.com/nFVtEo

std::string trim(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it))
        it++;

    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit))
        rit++;

    return std::string(it, rit.base());
}
Tamtam answered 11/2, 2014 at 10:15 Comment(1)
How this works: This is a copy-like solution - it finds position of first character that is not space(it) and reverse: position of the character after which there are only spaces(rit) - after that it returns a newly created string == a copy of the part of original string - a part based on those iterators...Hybrid
G
28

With C++17 you can use basic_string_view::remove_prefix and basic_string_view::remove_suffix:

std::string_view trim(std::string_view s)
{
    s.remove_prefix(std::min(s.find_first_not_of(" \t\r\v\n"), s.size()));
    s.remove_suffix(std::min(s.size() - s.find_last_not_of(" \t\r\v\n") - 1, s.size()));

    return s;
}

A nice alternative:

std::string_view ltrim(std::string_view s)
{
    s.remove_prefix(std::distance(s.cbegin(), std::find_if(s.cbegin(), s.cend(),
         [](int c) {return !std::isspace(c);})));

    return s;
}

std::string_view rtrim(std::string_view s)
{
    s.remove_suffix(std::distance(s.crbegin(), std::find_if(s.crbegin(), s.crend(),
        [](int c) {return !std::isspace(c);})));

    return s;
}

std::string_view trim(std::string_view s)
{
    return ltrim(rtrim(s));
}
Gird answered 25/1, 2019 at 11:13 Comment(2)
I am not sure what you are testing, but in your example std::find_first_not_of will return std::string::npos and std::string_view::size will return 4. The minimum is obviously four, the number of elements to be removed by std::string_view::remove_prefix. Both gcc 9.2 and clang 9.0 handle this correctly: godbolt.org/z/DcZbFHGird
std::find_if_not and may be even better, also with a predicate like ascii::is_space<char>Calyces
S
27

I like tzaman's solution, the only problem with it is that it doesn't trim a string containing only spaces.

To correct that 1 flaw, add a str.clear() in between the 2 trimmer lines

std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;
Subjectify answered 5/7, 2010 at 6:37 Comment(5)
Nice :) the problem with both our solutions, though, is that they'll trim both ends; can't make an ltrim or rtrim like this.Claqueur
Good, but can't deal with string with internal whitespace. e.g. trim( abc def") -> abc, only abc left.Gehman
A good solution if you know there won't be any internal whitespace!Lamdin
This is nice and easy but it is also quite slow as the string gets copied into and out of the std::stringstream.Homely
A classic trim is NOT supposed to remove internal whitespace.Backbencher
D
20

In the case of an empty string, your code assumes that adding 1 to string::npos gives 0. string::npos is of type string::size_type, which is unsigned. Thus, you are relying on the overflow behaviour of addition.

Dialectic answered 19/10, 2008 at 19:26 Comment(2)
You're phrasing that as if it's bad. Signed integer overflow behavior is bad.Tannen
Adding 1 to std::string::npos must give 0 according to the C++ Standard. So it is a good assumption that can be absolutely relied upon.Homely
D
17
str.erase(0, str.find_first_not_of("\t\n\v\f\r ")); // left trim
str.erase(str.find_last_not_of("\t\n\v\f\r ") + 1); // right trim

Try it online!

Depside answered 22/12, 2020 at 6:46 Comment(0)
P
15
s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
s.erase(s.find_last_not_of(" \n\r\t")+1);   
Prefer answered 13/10, 2015 at 10:2 Comment(1)
It would be slightly more efficient if you do those in the opposite order and trim from the right first before invoking a shift by trimming the left.Homely
S
14

Hacked off of Cplusplus.com

std::string choppa(const std::string &t, const std::string &ws)
{
    std::string str = t;
    size_t found;
    found = str.find_last_not_of(ws);
    if (found != std::string::npos)
        str.erase(found+1);
    else
        str.clear();            // str is all whitespace

    return str;
}

This works for the null case as well. :-)

Sawbuck answered 19/10, 2008 at 20:47 Comment(2)
This is just rtrim, not ltrimSingleness
^ do you mind using find_first_not_of? It's relatively easy to modify it.Grefe
A
11

My solution based on the answer by @Bill the Lizard.

Note that these functions will return the empty string if the input string contains nothing but whitespace.

const std::string StringUtils::WHITESPACE = " \n\r\t";

std::string StringUtils::Trim(const std::string& s)
{
    return TrimRight(TrimLeft(s));
}

std::string StringUtils::TrimLeft(const std::string& s)
{
    size_t startpos = s.find_first_not_of(StringUtils::WHITESPACE);
    return (startpos == std::string::npos) ? "" : s.substr(startpos);
}

std::string StringUtils::TrimRight(const std::string& s)
{
    size_t endpos = s.find_last_not_of(StringUtils::WHITESPACE);
    return (endpos == std::string::npos) ? "" : s.substr(0, endpos+1);
}
Afore answered 24/5, 2013 at 21:11 Comment(0)
R
11

Here is a solution for trim with regex

#include <string>
#include <regex>

string trim(string str){
    return regex_replace(str, regex("(^[ ]+)|([ ]+$)"),"");
}
Rummy answered 8/11, 2019 at 19:59 Comment(1)
I think I will use this solution because it's a single line of code. I suggest to include also '\n', which is considered a whitespace, into the regex: "(^[ \n]+)|([ \n]+$)".Ischium
D
10

With C++11 also came a regular expression module, which of course can be used to trim leading or trailing spaces.

Maybe something like this:

std::string ltrim(const std::string& s)
{
    static const std::regex lws{"^[[:space:]]*", std::regex_constants::extended};
    return std::regex_replace(s, lws, "");
}

std::string rtrim(const std::string& s)
{
    static const std::regex tws{"[[:space:]]*$", std::regex_constants::extended};
    return std::regex_replace(s, tws, "");
}

std::string trim(const std::string& s)
{
    return ltrim(rtrim(s));
}
Demark answered 27/8, 2013 at 12:11 Comment(0)
U
9

My answer is an improvement upon the top answer for this post that trims control characters as well as spaces (0-32 and 127 on the ASCII table).

std::isgraph determines if a character has a graphical representation, so you can use this to alter Evan's answer to remove any character that doesn't have a graphical representation from either side of a string. The result is a much more elegant solution:

#include <algorithm>
#include <functional>
#include <string>

/**
 * @brief Left Trim
 *
 * Trims whitespace from the left end of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& ltrim(std::string& s) {
  s.erase(s.begin(), std::find_if(s.begin(), s.end(),
    std::ptr_fun<int, int>(std::isgraph)));
  return s;
}

/**
 * @brief Right Trim
 *
 * Trims whitespace from the right end of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& rtrim(std::string& s) {
  s.erase(std::find_if(s.rbegin(), s.rend(),
    std::ptr_fun<int, int>(std::isgraph)).base(), s.end());
  return s;
}

/**
 * @brief Trim
 *
 * Trims whitespace from both ends of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& trim(std::string& s) {
  return ltrim(rtrim(s));
}

Note: Alternatively you should be able to use std::iswgraph if you need support for wide characters, but you will also have to edit this code to enable std::wstring manipulation, which is something that I haven't tested (see the reference page for std::basic_string to explore this option).

Uphemia answered 21/3, 2015 at 17:14 Comment(1)
std::ptr_fun Is deprecatedGathers
F
8

This is what I use. Just keep removing space from the front, and then, if there's anything left, do the same from the back.

void trim(string& s) {
    while(s.compare(0,1," ")==0)
        s.erase(s.begin()); // remove leading whitespaces
    while(s.size()>0 && s.compare(s.size()-1,1," ")==0)
        s.erase(s.end()-1); // remove trailing whitespaces
}
Farant answered 12/11, 2013 at 22:56 Comment(0)
A
8

An elegant way of doing it can be like

std::string & trim(std::string & str)
{
   return ltrim(rtrim(str));
}

And the supportive functions are implemented as:

std::string & ltrim(std::string & str)
{
  auto it =  std::find_if( str.begin() , str.end() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
  str.erase( str.begin() , it);
  return str;   
}

std::string & rtrim(std::string & str)
{
  auto it =  std::find_if( str.rbegin() , str.rend() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
  str.erase( it.base() , str.end() );
  return str;   
}

And once you've all these in place, you can write this as well:

std::string trim_copy(std::string const & str)
{
   auto s = str;
   return ltrim(rtrim(s));
}
Alanalana answered 18/8, 2015 at 11:21 Comment(0)
S
8

Trim C++11 implementation:

static void trim(std::string &s) {
     s.erase(s.begin(), std::find_if_not(s.begin(), s.end(), [](char c){ return std::isspace(c); }));
     s.erase(std::find_if_not(s.rbegin(), s.rend(), [](char c){ return std::isspace(c); }).base(), s.end());
}
Sudorific answered 14/10, 2015 at 15:51 Comment(0)
E
7

I guess if you start asking for the "best way" to trim a string, I'd say a good implementation would be one that:

  1. Doesn't allocate temporary strings
  2. Has overloads for in-place trim and copy trim
  3. Can be easily customized to accept different validation sequences / logic

Obviously there are too many different ways to approach this and it definitely depends on what you actually need. However, the C standard library still has some very useful functions in <string.h>, like memchr. There's a reason why C is still regarded as the best language for IO - its stdlib is pure efficiency.

inline const char* trim_start(const char* str)
{
    while (memchr(" \t\n\r", *str, 4))  ++str;
    return str;
}
inline const char* trim_end(const char* end)
{
    while (memchr(" \t\n\r", end[-1], 4)) --end;
    return end;
}
inline std::string trim(const char* buffer, int len) // trim a buffer (input?)
{
    return std::string(trim_start(buffer), trim_end(buffer + len));
}
inline void trim_inplace(std::string& str)
{
    str.assign(trim_start(str.c_str()),
        trim_end(str.c_str() + str.length()));
}

int main()
{
    char str [] = "\t \nhello\r \t \n";

    string trimmed = trim(str, strlen(str));
    cout << "'" << trimmed << "'" << endl;

    system("pause");
    return 0;
}
Encrimson answered 16/11, 2013 at 20:10 Comment(0)
M
7

For what it's worth, here is a trim implementation with an eye towards performance. It's much quicker than many other trim routines I've seen around. Instead of using iterators and std::finds, it uses raw c strings and indices. It optimizes the following special cases: size 0 string (do nothing), string with no whitespace to trim (do nothing), string with only trailing whitespace to trim (just resize the string), string that's entirely whitespace (just clear the string). And finally, in the worst case (string with leading whitespace), it does its best to perform an efficient copy construction, performing only 1 copy and then moving that copy in place of the original string.

void TrimString(std::string & str)
{ 
    if(str.empty())
        return;

    const auto pStr = str.c_str();

    size_t front = 0;
    while(front < str.length() && std::isspace(int(pStr[front]))) {++front;}

    size_t back = str.length();
    while(back > front && std::isspace(int(pStr[back-1]))) {--back;}

    if(0 == front)
    {
        if(back < str.length())
        {
            str.resize(back - front);
        }
    }
    else if(back <= front)
    {
        str.clear();
    }
    else
    {
        str = std::move(std::string(str.begin()+front, str.begin()+back));
    }
}
Mandymandych answered 5/1, 2015 at 21:21 Comment(1)
@bmgda perhaps theoretically the fastest version might be having this signature: extern "C" void string_trim ( char ** begin_, char ** end_ ) ... Catch my drift?Selenodont
A
7

This can be done more simply in C++11 due to the addition of back() and pop_back().

while ( !s.empty() && isspace(s.back()) ) s.pop_back();
Antoinetteanton answered 31/1, 2016 at 18:54 Comment(1)
The approach suggested by the OP isn't bad either -- just a bit harder to follow.Antoinetteanton
H
6

Contributing my solution to the noise. trim defaults to creating a new string and returning the modified one while trim_in_place modifies the string passed to it. The trim function supports c++11 move semantics.

#include <string>

// modifies input string, returns input

std::string& trim_left_in_place(std::string& str) {
    size_t i = 0;
    while(i < str.size() && isspace(str[i])) { ++i; };
    return str.erase(0, i);
}

std::string& trim_right_in_place(std::string& str) {
    size_t i = str.size();
    while(i > 0 && isspace(str[i - 1])) { --i; };
    return str.erase(i, str.size());
}

std::string& trim_in_place(std::string& str) {
    return trim_left_in_place(trim_right_in_place(str));
}

// returns newly created strings

std::string trim_right(std::string str) {
    return trim_right_in_place(str);
}

std::string trim_left(std::string str) {
    return trim_left_in_place(str);
}

std::string trim(std::string str) {
    return trim_left_in_place(trim_right_in_place(str));
}

#include <cassert>

int main() {

    std::string s1(" \t\r\n  ");
    std::string s2("  \r\nc");
    std::string s3("c \t");
    std::string s4("  \rc ");

    assert(trim(s1) == "");
    assert(trim(s2) == "c");
    assert(trim(s3) == "c");
    assert(trim(s4) == "c");

    assert(s1 == " \t\r\n  ");
    assert(s2 == "  \r\nc");
    assert(s3 == "c \t");
    assert(s4 == "  \rc ");

    assert(trim_in_place(s1) == "");
    assert(trim_in_place(s2) == "c");
    assert(trim_in_place(s3) == "c");
    assert(trim_in_place(s4) == "c");

    assert(s1 == "");
    assert(s2 == "c");
    assert(s3 == "c");
    assert(s4 == "c");  
}
Halfon answered 6/5, 2014 at 3:24 Comment(0)
C
5

Here's what I came up with:

std::stringstream trimmer;
trimmer << str;
trimmer >> str;

Stream extraction eliminates whitespace automatically, so this works like a charm.
Pretty clean and elegant too, if I do say so myself. ;)

Claqueur answered 27/5, 2010 at 7:29 Comment(1)
Hmm; this assumes that the string has no internal whitespace (e.g. spaces). The OP only said he wanted to trim whitespace on the left or right.Galasyn
F
3

I'm not sure if your environment is the same, but in mine, the empty string case will cause the program to abort. I would either wrap that erase call with an if(!s.empty()) or use Boost as already mentioned.

Farhi answered 19/10, 2008 at 20:3 Comment(0)
D
3

Here is my version:

size_t beg = s.find_first_not_of(" \r\n");
return (beg == string::npos) ? "" : in.substr(beg, s.find_last_not_of(" \r\n") - beg);
Dode answered 31/8, 2016 at 14:41 Comment(1)
You are missing the last character. A +1 in the length solves thisCardwell
S
3

Here's a solution easy to understand for beginners not used to write std:: everywhere and not yet familiar with const-correctness, iterators, STL algorithms, etc...

#include <string>
#include <cctype> // for isspace
using namespace std;


// Left trim the given string ("  hello!  " --> "hello!  ")
string left_trim(string str) {
    int numStartSpaces = 0;
    for (int i = 0; i < str.length(); i++) {
        if (!isspace(str[i])) break;
        numStartSpaces++;
    }
    return str.substr(numStartSpaces);
}

// Right trim the given string ("  hello!  " --> "  hello!")
string right_trim(string str) {
    int numEndSpaces = 0;
    for (int i = str.length() - 1; i >= 0; i--) {
        if (!isspace(str[i])) break;
        numEndSpaces++;
    }
    return str.substr(0, str.length() - numEndSpaces);
}

// Left and right trim the given string ("  hello!  " --> "hello!")
string trim(string str) {
    return right_trim(left_trim(str));
}

Hope it helps...

Selectivity answered 8/12, 2016 at 12:14 Comment(0)
B
2

The above methods are great, but sometimes you want to use a combination of functions for what your routine considers to be whitespace. In this case, using functors to combine operations can get messy so I prefer a simple loop I can modify for the trim. Here is a slightly modified trim function copied from the C version here on SO. In this example, I am trimming non alphanumeric characters.

string trim(char const *str)
{
  // Trim leading non-letters
  while(!isalnum(*str)) str++;

  // Trim trailing non-letters
  end = str + strlen(str) - 1;
  while(end > str && !isalnum(*end)) end--;

  return string(str, end+1);
}
Bookman answered 24/11, 2009 at 19:26 Comment(1)
forgot const char* end ?Newness
A
2

What about this...?

#include <iostream>
#include <string>
#include <regex>

std::string ltrim( std::string str ) {
    return std::regex_replace( str, std::regex("^\\s+"), std::string("") );
}

std::string rtrim( std::string str ) {
    return std::regex_replace( str, std::regex("\\s+$"), std::string("") );
}

std::string trim( std::string str ) {
    return ltrim( rtrim( str ) );
}

int main() {

    std::string str = "   \t  this is a test string  \n   ";
    std::cout << "-" << trim( str ) << "-\n";
    return 0;

}

Note: I'm still relatively new to C++, so please forgive me if I'm off base here.

Arni answered 3/10, 2013 at 19:32 Comment(3)
Using regex for trimming is a bit of an overkill.Lithosphere
Is it much more CPU intensive than some of the other options presented?Arni
Relevant question on that matter (cc @user1095108): #68649091Chickasaw
S
2

Here is a straight forward implementation. For such a simple operation, you probably should not be using any special constructs. The build-in isspace() function takes care of various forms of white characters, so we should take advantage of it. You also have to consider special cases where the string is empty or simply a bunch of spaces. Trim left or right could be derived from the following code.

string trimSpace(const string &str) {
   if (str.empty()) return str;
   string::size_type i,j;
   i=0;
   while (i<str.size() && isspace(str[i])) ++i;
   if (i == str.size())
      return string(); // empty string
   j = str.size() - 1;
   //while (j>0 && isspace(str[j])) --j; // the j>0 check is not needed
   while (isspace(str[j])) --j
   return str.substr(i, j-i+1);
}
Samsun answered 23/3, 2016 at 4:40 Comment(0)
H
1

This version trims internal whitespace and non-alphanumerics:

static inline std::string &trimAll(std::string &s)
{   
    if(s.size() == 0)
    {
        return s;
    }

    int val = 0;
    for (int cur = 0; cur < s.size(); cur++)
    {
        if(s[cur] != ' ' && std::isalnum(s[cur]))
        {
            s[val] = s[cur];
            val++;
        }
    }
    s.resize(val);
    return s;
}
Hayne answered 24/8, 2010 at 17:57 Comment(0)
G
1

Yet another option - removes one or more characters from both ends.

string strip(const string& s, const string& chars=" ") {
    size_t begin = 0;
    size_t end = s.size()-1;
    for(; begin < s.size(); begin++)
        if(chars.find_first_of(s[begin]) == string::npos)
            break;
    for(; end > begin; end--)
        if(chars.find_first_of(s[end]) == string::npos)
            break;
    return s.substr(begin, end-begin+1);
}
Gameness answered 27/3, 2013 at 0:19 Comment(0)
R
1

As I wanted to update my old C++ trim function with a C++ 11 approach I have tested a lot of the posted answers to the question. My conclusion is that I keep my old C++ solution!

It is the fastest one by large, even adding more characters to check (e.g. \r\n I see no use case for \f\v) is still faster than the solutions using algorithm.

std::string & trimMe (std::string & str)
{
   // right trim
   while (str.length () > 0 && (str [str.length ()-1] == ' ' || str [str.length ()-1] == '\t'))
      str.erase (str.length ()-1, 1);

   // left trim
   while (str.length () > 0 && (str [0] == ' ' || str [0] == '\t'))
      str.erase (0, 1);
   return str;
}
Redroot answered 15/3, 2016 at 0:11 Comment(0)
S
1

Ok this maight not be the fastest but it's... simple.

str = "   aaa    ";
int len = str.length();
// rtrim
while(str[len-1] == ' ') { str.erase(--len,1); }
// ltrim
while(str[0] == ' ') { str.erase(0,1); }
Savaii answered 7/9, 2019 at 4:27 Comment(2)
This is incorrect. You need to do invert the ltrim and rtrim for this to work.Knuckleduster
This is incorrect: it throws an array exception error if str=="" or str==" " (three spaces). To fix, add a check !copy.empty() && as the first check for both while loops. The rtrim implementation shifts the entire string down at every step, which could be inefficient. If efficiency is important, suggest other answers that do a scan followed by a single trim operation.Razo
T
1

you can you use this function to trim you string in c++

void trim(string& str){
   while(str[0] == ' ') str.erase(str.begin());
   while(str[str.size() - 1] == ' ') str.pop_back();
}
Transfuse answered 20/5, 2022 at 12:4 Comment(2)
If using C++17 maybe consider str[str.size() - 1] -> str.back(). But a bigger problem is that this has undefined behavior on an empty or all-space string.Auster
This does not respect whitespace characters like '\n' or '\t`.Unlikely
N
0
std::string trim( std::string && str )
{
    size_t end = str.find_last_not_of( " \n\r\t" );
    if ( end != std::string::npos )
        str.resize( end + 1 );

    size_t start = str.find_first_not_of( " \n\r\t" );
    if ( start != std::string::npos )
        str = str.substr( start );

    return std::move( str );
}
Nasia answered 28/11, 2013 at 9:57 Comment(0)
A
0

This any good? (Cause this post totally needs another answer :)

string trimBegin(string str)
{
    string whites = "\t\r\n ";
    int i = 0;
    while (whites.find(str[i++]) != whites::npos);
    str.erase(0, i);
    return str;
}

Similar case for the trimEnd, just reverse the polari- er, indices.

Arran answered 2/4, 2014 at 15:54 Comment(1)
I'm not totally sure what the overhead to using a temporary string for comparison's sake is, but I think I would have preferred to use while (isspace(str[i++])); for clarity. The biggest motivating factor is actually that I had to read some documentation to understand the code because I originally thought this had a complexity order of O(n^2) (which it, as you know, doesn't). The code could then be reduced to string trimBegin(string str) { size_t i = 0; while(isspace(str[i++]); return str.erase(0, i); }Halfon
L
0

I'm using this one:

void trim(string &str){
    int i=0;

    //left trim
    while (isspace(str[i])!=0)
        i++;
    str = str.substr(i,str.length()-i);

    //right trim
    i=str.length()-1;
    while (isspace(str[i])!=0)
        i--;
    str = str.substr(0,i+1);
}
Letdown answered 11/10, 2015 at 2:39 Comment(0)
F
0

I know this is a very old question, but I have added a few lines of code to yours and it trims whitespace from both ends.

void trim(std::string &line){

    auto val = line.find_last_not_of(" \n\r\t") + 1;

    if(val == line.size() || val == std::string::npos){
        val = line.find_first_not_of(" \n\r\t");
        line = line.substr(val);
    }
    else
        line.erase(val);
}
Firmin answered 10/7, 2018 at 22:31 Comment(0)
S
0

Below is one pass(may be two pass) solution. It goes over the white spaces part of string twice and non-whitespace part once.

void trim(std::string& s) {                                                                                                                                                                                                               
    if (s.empty())                                                                                                                                                                                                                        
        return;                                                                                                                                                                                                                           

    int l = 0, r = s.size()  - 1;                                                                                                                                                                                                         

    while (l < s.size() && std::isspace(s[l++])); // l points to first non-whitespace char.                                                                                                                                               
    while (r >= 0 && std::isspace(s[r--])); // r points to last non-whitespace char.                                                                                                                                                      

    if (l > r)                                                                                                                                                                                                                            
        s = "";                                                                                                                                                                                                                           
    else {                                                                                                                                                                                                                                
        l--;                                                                                                                                                                                                                              
        r++;                                                                                                                                                                                                                              
        int wi = 0;                                                                                                                                                                                                                       
        while (l <= r)                                                                                                                                                                                                                    
            s[wi++] = s[l++];                                                                                                                                                                                                             
        s.erase(wi);                                                                                                                                                                                                                      
    }                                                                                                                                                                                                                                     
    return;                                                                                                                                                                                                                               
}                                          
Sorrow answered 26/9, 2018 at 19:31 Comment(0)
M
0

using std::find_if_not and reverse iterator (no +1/-1 adjustments) and returning number of spaces trimmed

// returns number of spaces removed
std::size_t RoundTrim(std::string& s)
{
    auto const beforeTrim{ s.size() };

    auto isSpace{ [](auto const& e) { return std::isspace(e); } };

    s.erase(cbegin(s), std::find_if_not(cbegin(s), cend(s), isSpace));
    s.erase(std::find_if_not(crbegin(s), crend(s), isSpace).base(), end(s));

    return beforeTrim - s.size();
};
Meistersinger answered 24/9, 2022 at 7:59 Comment(1)
This is strictly worse than using std::string::find_first_not_of.Unlikely
A
0

While most of the above solution would work well, but I would like to propose a solution I use for specific use case where space are placed repetitively in a chaos order at either left, right or both side of the actual content.

std::string x = " \t\n\n\t\r \r\v\tabc\n\t\n\r\v ";

/* CPP17 */
#include <iostream>
#include <string>
#include <array>

void ltrim(std::string& str, const std::array<char,5> &spaces)
{
    size_t prev = str.length();
    size_t counter;
    
    do {
        int counter = 0;
        for (char space : spaces)
        {
            str.erase(0, str.find_first_not_of(space));       //prefixing spaces
            size_t current = str.length();
            if (prev == current)
                counter += 1;
            else
                prev = current;
    } while (counter != spaces.size();
}

void rtrim(std::string& str, const std::array<char,5> &spaces)
{
    size_t prev = str.length();
    size_t counter;
    
    do {
        int counter = 0;
        for (char space : spaces)
        {
            str.erase(str.find_last_not_of(space)+1);         //suffixing spaces
            size_t current = str.length();
            if (prev == current)
                counter += 1;
            else
                prev = current;
    } while (counter != spaces.size();
}

int main()
{
    const std::array<char,5> spaces = {' ', '\n', '\t', '\v', '\r'};
    std::string x = " \t\n\n\t\r \r\v\tActual Content\n\t\n\r\v  ";
    ltrim(x, spaces);
    rtrim(x, spaces);
    std::cout << '[' << x << ']' << std::endl; // [Actual Content]
    return 0;
}
Anybody answered 10/1, 2024 at 6:7 Comment(0)
N
-1

c++11:

int i{};
string s = " h e ll \t\n  o";
string trim = " \n\t";

while ((i = s.find_first_of(trim)) != -1)
    s.erase(i,1);

cout << s;

output:

hello

works fine also with empty strings

Newson answered 10/2, 2016 at 16:37 Comment(1)
Your code removes all the spaces even between non-spaces. That's not what "trim" is supposed to doPasteboard
Q
-1

The accepted answer and even Boost's version did not work for me, so I wrote the following version:

std::string trim(const std::string& input) {
    std::stringstream string_stream;
    for (const auto character : input) {
        if (!isspace(character)) {
            string_stream << character;
        }
    }

    return string_stream.str();
}

This will remove any whitespace character from anywhere in the string and return a new copy of the string.

Quinone answered 15/2, 2020 at 22:35 Comment(0)
R
-1

why not use lambda?

auto no_space = [](char ch) -> bool {
  return !std::isspace<char>(ch, std::locale::classic());
};
auto ltrim = [](std::string& s) -> std::string& {
  s.erase(s.begin(), std::find_if(s.begin(), s.end(), no_space));
  return s;
};
auto rtrim = [](std::string& s) -> std::string& {
  s.erase(std::find_if(s.rbegin(), s.rend(), no_space).base(), s.end());
  return s;
};
auto trim_copy = [](std::string s) -> std::string& { return ltrim(rtrim(s)); };
auto trim = [](std::string& s) -> std::string& { return ltrim(rtrim(s)); };
Rogan answered 5/8, 2020 at 17:46 Comment(0)
I
-1

Trims both ends.

string trim(const std::string &str){
    string result = "";
    size_t endIndex = str.size();
    while (endIndex > 0 && isblank(str[endIndex-1]))
        endIndex -= 1;
    for (size_t i=0; i<endIndex ; i+=1){
        char ch = str[i];
        if (!isblank(ch) || result.size()>0)
            result += ch;
    }
   return result;
}
Insomnolence answered 19/8, 2020 at 22:11 Comment(0)
A
-1

Poor man's string trim (spaces only):

std::string trimSpaces(const std::string& str)
{
    int start, len;
    
    for (start = 0; start < str.size() && str[start] == ' '; start++);
    for (len = str.size() - start; len > 0 && str[start + len - 1] == ' '; len--);
    
    return str.substr(start, len);
}
Athena answered 15/1, 2021 at 3:53 Comment(0)
S
-1

Trim left and right:

trimmed_str = std::regex_replace(original_str, std::regex{R"(^\s+|\s+$)"}, "");

Trim right:

trimmed_str = std::regex_replace(original_str, std::regex{R"(\s+$)"}, "");

Trim left:

trimmed_str = std::regex_replace(original_str, std::regex{R"(^\s+)"}, "");

Note: this generalizes this answer to work on all boundary white space (not just " "), so it will remove tabs, newlines, etc., as a trim function should.

Screens answered 11/10, 2023 at 11:2 Comment(0)
O
-2

I have read most of the answers but did not found anyone making use of istringstream

std::string text = "Let me split this into words";

std::istringstream iss(text);
std::vector<std::string> results((std::istream_iterator<std::string>(iss)),
                                 std::istream_iterator<std::string>());

The result is vector of words and it can deal with the strings having internal whitespace too, Hope this helped.

Overblown answered 15/7, 2020 at 17:6 Comment(0)
D
-3

It seems I'm really late to the party - I can't believe this was asked 7 years ago!

Here's my take on the problem. I'm working on a project and I didn't want to go through the trouble of using Boost right now.

std::string trim(std::string str) {
    if(str.length() == 0) return str;

    int beg = 0, end = str.length() - 1;
    while (str[beg] == ' ') {
        beg++;
    }

    while (str[end] == ' ') {
        end--;
    }

    return str.substr(beg, end - beg + 1);
}

This solution will trim from the left and the right.

Dishevel answered 6/12, 2015 at 1:29 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.