876

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 end-cases where it might fail?

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

Angie Quijano
  • 3,308
  • 3
  • 20
  • 30
Milan Babuškov
  • 55,232
  • 47
  • 119
  • 176
  • 606
    The answers to this question are a testament to how lacking the C++ standard library is. – Idan K Aug 24 '10 at 18:15
  • 89
    @IdanK And it still doesn't have this function in C++11. – quantum Mar 10 '12 at 18:10
  • 45
    @IdanK: Great, isn't it! Look at all the competing options we now have at our disposal, unencumbered by a single person's idea of "_the_ way that we must do it"! – Lightness Races in Orbit Jan 30 '13 at 01:40
  • 10
    @LightnessRacesinOrbit: Having convenient functions in the library does not preclude being unable to do things another way. The library has `vector`, but we're not forced to only use vector. – Mooing Duck Mar 27 '13 at 00:34
  • 5
    @MooingDuck: A choice of types is one thing; a choice of functionality _within_ a given type is another, since providing those choices can create limitations on the rest of the type and on its implementation. – Lightness Races in Orbit Mar 27 '13 at 01:14
  • 65
    @LightnessRacesinOrbit functionality within a type, well that's a design decision, and adding a trim function to a string might (at least under c++) not be the best solution anyway - but not providing any standard way to do it, instead letting everyone fret over the same such small issues over and over again, is certainly not helping anyone either – codeling Mar 29 '13 at 11:01
  • 2
    I really don't know what you are about. There **is** a standard way to do these things in C++: By including the specialized library of your choice and using *its* trim function, alongside with all the other specialized functions for your problem domain. – DevSolar Jul 31 '13 at 17:51
  • 8
    @DevSolar, there is no *standard* way to do it, because the std::string in *standard* library ("std" stands for *standard* FYI) does not provide it. You have to use a 3rd party library. – Milan Babuškov Jul 31 '13 at 21:16
  • 4
    @MilanBabuškov: Languages like Java or C# try to provide "all singing, all dancing" libraries as part of the language proper. Languages like C or C++ provide only merest basics, and leave it to the specialists to come up with domain solutions. That is their "standard" approach. I happen to be the maintainer of a proprietary C++ library that does tokenize, trim, analyze, compare and deduplicate international addresses, and I can tell you, having `trim()` in `std::` would not have saved me any work since my problem domain required me to work with very non-`std::`/STL strings in the first place. – DevSolar Aug 01 '13 at 05:54
  • 31
    You can question why trimming functions are not built in into the `std::string` class, when it is functions like these that make other languages so nice to use (Python for example). – HelloGoodbye Jan 25 '14 at 18:04
  • 5
    @IdanK, at least one influential C++ guru thinks that there's already *too much* in the standard string class. Not sure I agree but it makes for interesting reading: http://www.gotw.ca/gotw/084.htm – Mark Ransom Mar 13 '14 at 18:47
  • 3
    @MarkRansom, Yep there's arguments why it shouldn't be _in_ the string class, but it could be a free function, like std::trim(...), just like in boost. – Ela782 May 29 '14 at 16:11
  • 4
    There's too much of what Stepanov thought should be in STL to make it generic vs. and too little of what *needs* to be in STL to make it useful; an example of a disconnect in understanding and what happens when something is designed based on theory rather than requirements. (but I still use it all the time :) – Nick Aug 22 '14 at 11:49
  • 19
    No problem there, I'll write my own 1.000.000th version of it. Using a virtual `trimmable` interface and wrapping it into half a dozen layers of nice generic templates, so that you can also trim monkeys and giraffes (possibly in place and/or using a custom allocator) with practically no coding overhead and lightning speed optimizations. – kuroi neko Sep 10 '14 at 01:14
  • 1. Something like boost should be new new STL. 2. C++ should have conditional compilation pragmas (like weak, but built in to the compiler ). And then C++ would finally be able to compete, again, on a level playing field with other languages for both usability AND portability – Erik Aronesty Feb 11 '15 at 16:29
  • 4
    Left-trim : `s = s.substr(s.find_first_not_of(" "));` – PinkTurtle Nov 17 '15 at 14:45
  • 22
    It seems to me that the single line answer provided by the questioner beats most if not all of the elaborate answers provided below – Bill Forster Jan 21 '16 at 04:34
  • 1
    There is one thing thing which has irritated me about C++ even as I've gained proficiency. If importing other libraries for higher levels of abstraction is expected, why does the standard not specify modules? It took me 6 years to overcome the numerous obstacles and finally compile, link, and include external libraries without relying on the (often outdated) quick start guides. – Aaron3468 Aug 15 '16 at 13:42
  • 1
    For anyone in the .Net world, here's a nice benchmarking article on some of the fastest ways to trim strings there: [Fastest Way To Trim Strings in C# .Net](http://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring/27788112#27788112) –  Feb 23 '17 at 00:01
  • Long live with Java, API is the best. – Bahramdun Adil Apr 16 '17 at 15:17
  • 7
    This kind of question shows both the best and the worst of C++. – Xam Jul 27 '18 at 23:28
  • 4
    Still no support of ltrim and rtrim in c++17?! – ConsistentProgrammer Mar 28 '19 at 14:25
  • 1
    I started coding with C++ in 2014. Switched to JS, Java, and PHP soon after. Coming back to C++ 5 years later, it's disappointing to see how little the standard library has improved. – Oloff Biermann Aug 01 '19 at 16:46
  • 1
    Does anybody know, why c++ has these basic problems? In comarison with c# is work with string in c++ unbelievable poor. I cannot believe that in c++ everybody have to write his own function for string Trim, TrimStart, TrimEnd, Split, Join, .... – bmi Dec 30 '20 at 11:13
  • 1
    @bmi QtCore is a good alternative to the standard library, for string, containers, etc. Good side is that Qt has its whole ecosystem of libraries, bad side is that is t is not the standard (reduces compatibility). The standard library implements very low level concepts and is awkwardly designed. – Kiruahxh Dec 30 '20 at 14:08

48 Answers48

718

EDIT Since c++17, some parts of the standard library were removed. Fortunately, starting with c++11, we have lambdas which are a superior solution.

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

// trim from start (in place)
static 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)
static 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());
}

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

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

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

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

Thanks to https://stackoverflow.com/a/44973498/524503 for bringing up the modern solution.

Original answer:

I tend to use one of these 3 for my trimming needs:

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

// trim from start
static 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
static 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
static inline std::string &trim(std::string &s) {
    return ltrim(rtrim(s));
}

They are fairly self-explanatory and work very well.

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

EDIT: 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)
static 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)
static 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());
}

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

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

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

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

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

Evan Teran
  • 80,654
  • 26
  • 169
  • 231
  • 30
    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. – Tom Jul 22 '12 at 04:36
  • 5
    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. – Marco Leogrande Sep 06 '12 at 00:40
  • 4
    Note that with isspace you can easily get undefined behaviour with non-ASCII characters http://stacked-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f – R. Martinho Fernandes Feb 04 '13 at 13:52
  • @R.MartinhoFernandes, true, a solution that I've used in the past is this template: `template int safe_ctype(unsigned char c) { return F(c); }` which can be used like this: `safe_ctype(ch);` – Evan Teran Feb 04 '13 at 17:31
  • 10
    Why the static? Is this where an anonymous namespace would be preferred? – Trevor Hickey Sep 30 '13 at 09:29
  • 3
    @TrevorHickey, sure, you could use an anonymous namespace instead if you prefer. – Evan Teran Sep 30 '13 at 11:17
  • 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](https://gist.github.com/superwills/7723313), which implements these functions using only iterators – bobobobo Apr 19 '14 at 10:42
  • 2
    For naming consistency with `std::string` I'd recommend the names `trim_front` and `trim_back` – Evan Moran May 29 '14 at 05:59
  • 1
    One other suggestion would be to make this generic and support different forms of `std::basic_string`.
    `template` `inline void ltrim(std::basic_string& s) {...}`
    – Andrew Dec 08 '14 at 15:29
  • See [my answer](http://stackoverflow.com/a/29185584/1114073) below to support a wider range of characters to be trimmed. – Clay Freeman Mar 21 '15 at 21:12
  • Returning _and_ modifying the argument: Bad! :-) – Johannes Overmann Mar 08 '16 at 13:25
  • 1
    @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. – Evan Teran Mar 08 '16 at 17:01
  • @JohannesOvermann, I have added a edit to this answer to address your (and some others) concerns. – Evan Teran Mar 10 '16 at 18:30
  • @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. – underscore_d Sep 12 '16 at 15:42
  • @underscore_d 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". – Evan Teran Sep 14 '16 at 15:56
  • 1
    std::ptr_fun Is now deprecated – johnbakers Dec 08 '16 at 17:20
  • @johnbakers true, could use a cast or lambda in more modern code. – Evan Teran Dec 08 '16 at 18:18
  • Still don't understand why this has so many votes. Use find_last_not_of is easy and explanatory! – frankliuao Jun 30 '17 at 14:43
  • in rtime what is the purpose of base call to find_if?? – Arup Jul 06 '17 at 12:53
  • std::ptr_fun is deprecated in C++17. What should replace it? – user3717478 Jul 07 '17 at 14:12
  • @user3717478: Cross-posting to your own question on the topic: https://stackoverflow.com/a/44973511/560648. It was deprecated in C++11; _removed_ in C++17. – Lightness Races in Orbit Jul 07 '17 at 14:42
  • 1
    With C++17 it will probably be better to use `std::string_view` – WorldSEnder Aug 06 '17 at 14:44
  • @WorldSEnder 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. – smoothware Jan 19 '18 at 23:31
  • @tom Replace `int ch` with `unsigned char ch` in closure and it won't trigger assertion failure. – Zmey Apr 29 '19 at 21:42
  • 1
    Note that depending on the implementation, right-trimming a string before left-trimming might be faster than the other way around. – jotik Nov 02 '19 at 10:24
  • 1
    @Tom: Shift-JIS is not "international" if you live in a country that uses it. You mean non-ASCII. – Lightness Races in Orbit Nov 05 '19 at 17:17
  • 1
    @SagarKumar Edits improving existing answers are appreciated, but completely changing somebody’s code to something fundamentally different is absolutely not acceptable, especially if it’s objectively worse, and removes a lot of information. If you think it’s worthwhile you can always add your own answer instead. – Konrad Rudolph Feb 24 '20 at 17:55
  • 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: https://en.cppreference.com/w/cpp/string/byte/isspace – Rman Sep 15 '20 at 07:41
433

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.

LogicStuff
  • 18,687
  • 6
  • 49
  • 70
Leon Timmermans
  • 29,284
  • 2
  • 59
  • 110
  • 1
    What does boost use to determine if a character is whitespace? – Tom Dec 07 '08 at 21:22
  • 8
    It depends on the locale. My default locale (VS2005, en) means tabs, spaces, carriage returns, newlines, vertical tabs and form feeds are trimmed. – MattyT Jan 26 '09 at 13:11
  • 4
    I'm already using lots of boost, `#include #include #include ` but was worried about code bloat for adding in `` 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. – Rian Sanderson Jul 25 '11 at 05:36
  • @MattyT: What reference are you using for this list (determining if a character is whitespace)? – Faheem Mitha Dec 30 '11 at 04:04
  • 133
    Boost is such a massive hammer for such a tiny problem. – Casey Rodarmor Mar 27 '12 at 04:44
  • 153
    @rodarmor: Boost solves many tiny problems. It's a massive hammer that solves a lot. – Nicol Bolas May 25 '12 at 20:22
  • 131
    Boost is a set of hammers of many different sizes solving many different problems. – Ibrahim Jan 18 '13 at 11:14
  • 1
    Boost is the large Swiss Army knife that you have to jiggle and push to fit into your pocket. – gbmhunter Aug 21 '14 at 05:17
  • 4
    The only thing boost is practical for IMO is providing content for the standard library (ideas that get added from boost). – developerbmw Nov 06 '14 at 06:02
  • 2
    @Ibrahim Boost is THE Universal Trans-Continental Tool Building Factory Factory Factory http://discuss.joelonsoftware.com/default.asp?joel.3.219431.12 – José Ernesto Lara Rodríguez Nov 24 '15 at 18:43
  • 13
    @rodarmor You say that as if Boost is an all-or-nothing monolith, where including one of its headers somehow inflicts the entire thing on one's program. Which clearly isn't the case. Btw, I've never used Boost, fwiw. – underscore_d Sep 12 '16 at 15:45
  • 3
    @underscore_d but it is. It's never including "just one file". You end up including more source code from boost than your own project. – David Nogueira May 20 '19 at 10:34
  • 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. – Lieuwe Jul 16 '19 at 15:16
  • 1
    does not really answer the question which asks for std::string (not for boost or any other library ...) – hfrmobile Nov 04 '19 at 14:20
  • It's said above that Boost is an awfully big hammer for such a small problem--and I'd agree it is--but the other solutions have a non-trivial amount of code to write/paste and possibly maintain. Here we are 13 years after the question was asked and stdlib still hasn't caught up. There are no "small hammers" available. In the case where your project is already using Boost (which was my use case), just importing one more header is almost certainly a much better choice than writing all this out by hand. – GrandOpener May 11 '21 at 15:24
64

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.

Galik
  • 42,526
  • 3
  • 76
  • 100
  • if you change the order in `trim`, i.e. make it `rtrim(ltrim(s, t), t)` it will be slightly more efficient – CITBL Jan 07 '19 at 18:31
  • 2
    @CITBL The inner function is performed first so your way it will trim from the left *before* trimming from the right. I think that would be *less* efficient wouldn't it? – Galik Jan 07 '19 at 19:01
  • Exactly. My mistake – CITBL Jan 07 '19 at 19:12
  • 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. technically at that point you could make it ready for c++20 and mark it constexpr too as this implies inline – Beached Dec 02 '19 at 01:09
  • @Beached 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. – Galik Dec 02 '19 at 01:18
62

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 );
}
Bill the Lizard
  • 369,957
  • 201
  • 546
  • 842
58

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());
}
David G
  • 4,158
  • 1
  • 19
  • 16
  • 13
    Nice. +1 from me. Too bad C++11 did not introduce trim() into std::string and made life easier for everyone. – Milan Babuškov Jul 31 '13 at 17:41
  • 3
    I always want one function call to trim string, instead of implementing it – linquize Jan 17 '14 at 08:56
  • 25
    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);` – vmrob May 10 '14 at 22:52
  • 4
    +1 for probably the only answer with the implementation that only does one O(N) string copy. – Alexei Averchenko Aug 12 '14 at 09:12
  • 4
    @vmrob 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)` – johnbakers Dec 21 '16 at 15:50
  • @johnbakers It works in visual studio (cl 19), but not on clang (3.9.1) or gcc (6.3). – Zitrax Jan 23 '17 at 15:01
  • Also I notice that the number of assembly lines for this answer (1st function) is ~1100 lines while for example [this answer](http://stackoverflow.com/a/6500499/11722) is < 100 lines. Haven't investigated further than that though. – Zitrax Jan 23 '17 at 15:05
  • I'm wondering what't the meaning of `.base()` ? could you tell me, please – 小文件 Jun 11 '18 at 03:37
  • 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`. – David G Jun 14 '18 at 16:12
  • 1
    @vmrob No, you can't. `isspace` has two overloads. Moreover, taking the address of a function in the standard library is UB since C++20. – L. F. Jul 22 '19 at 13:17
  • @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: https://en.cppreference.com/w/cpp/string/byte/isspace – vmrob Sep 17 '20 at 20:00
  • 1
    @vmrob 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. – L. F. Sep 18 '20 at 10:16
  • @L.F. Ah, I see! Thanks for the info! – vmrob Sep 18 '20 at 18:29
46

Try this, it works for me.

inline std::string trim(std::string& str)
{
    str.erase(0, str.find_first_not_of(' '));       //prefixing spaces
    str.erase(str.find_last_not_of(' ')+1);         //surfixing spaces
    return str;
}
Andreas
  • 4,832
  • 6
  • 40
  • 49
user818330
  • 493
  • 4
  • 2
  • 12
    If your string contains no suffixing spaces, this will erase starting at npos+1 == 0, and you will delete the entire string. – mhsmith Oct 17 '12 at 03:51
  • 3
    @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. – Travis Oct 30 '13 at 14:46
  • 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. – Johannes Overmann Jan 10 '14 at 09:50
  • @JohannesOvermann: Why should it fail for all-whitespace strings? – robert Jan 22 '14 at 11:42
  • @robert: Argh, yes, ok. It works because npos+1 == 0 in practice. But I do not really like arithmetic with npos. :-) – Johannes Overmann Jan 26 '14 at 14:06
  • 6
    This should return std::string& to avoid unnecessarily invoking the copy constructor. – heksesang Oct 17 '14 at 10:49
  • 7
    I am confused why this returns a copy after modifying the return parameter? – Galik Jun 18 '15 at 00:51
  • @Galik 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. – MiloDC Dec 21 '17 at 01:43
  • 3
    @MiloDC My confusion is why return a copy *instead of* a reference. It makes more sense to me to return `std::string&`. – Galik Dec 21 '17 at 02:16
  • 1
    If you change the order (make it first to remove suffixing spaces then prefixing spaces) it will be more efficient. – CITBL Jan 07 '19 at 18:05
25

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());
}
Pushkoff
  • 381
  • 3
  • 3
  • 1
    Elegant solution for basic space trim at last... :) – jave.web Aug 26 '15 at 14:43
  • 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... – jave.web Aug 26 '15 at 14:52
  • Thank you, worked for me: std:string s = " Oh noez: space \r\n"; std::string clean = trim(s); – Alexx Roche Nov 19 '15 at 19:31
25

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;
17

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.

Greg Hewgill
  • 828,234
  • 170
  • 1,097
  • 1,237
  • 23
    You're phrasing that as if it's bad. *Signed* integer overflow behavior is bad. – MSalters Oct 20 '08 at 08:21
  • 3
    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. – Galik Sep 24 '18 at 22:51
16

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));
}
Phidelux
  • 1,572
  • 1
  • 24
  • 46
  • I am not sure what you are testing, but in your example [std::find_first_not_of](http://www.cplusplus.com/reference/string/string/find_first_not_of/) will return [std::string::npos](http://www.cplusplus.com/string::npos) and [std::string_view::size](https://en.cppreference.com/w/cpp/string/basic_string_view/size) will return 4. The minimum is obviously four, the number of elements to be removed by [std::string_view::remove_prefix](https://en.cppreference.com/w/cpp/string/basic_string_view/remove_prefix). Both gcc 9.2 and clang 9.0 handle this correctly: https://godbolt.org/z/DcZbFH – Phidelux Feb 24 '20 at 07:36
13

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. :-)

Jean-François Fabre
  • 126,787
  • 22
  • 103
  • 165
Paul Nathan
  • 37,919
  • 28
  • 110
  • 204
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);
}
Community
  • 1
  • 1
DavidRR
  • 15,000
  • 17
  • 89
  • 169
9

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));
}
Some programmer dude
  • 363,249
  • 31
  • 351
  • 550
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).

Community
  • 1
  • 1
Clay Freeman
  • 533
  • 5
  • 16
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
}
synaptik
  • 7,677
  • 13
  • 58
  • 89
8
s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
s.erase(s.find_last_not_of(" \n\r\t")+1);   
freeboy1015
  • 1,977
  • 2
  • 16
  • 14
  • 3
    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. – Galik Sep 24 '18 at 22:56
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));
    }
}
mbgda
  • 757
  • 5
  • 8
  • @bmgda perhaps theoretically the fastest version might be having this signature: extern "C" void string_trim ( char ** begin_, char ** end_ ) ... Catch my drift? –  Sep 19 '18 at 10:58
6

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));
}
jha-G
  • 1,944
  • 14
  • 29
6

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());
}
GutiMac
  • 2,058
  • 18
  • 26
5

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;
}
Jorma Rebane
  • 507
  • 4
  • 10
4

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();
Brent Bradburn
  • 40,766
  • 12
  • 126
  • 136
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.

Steve
  • 551
  • 4
  • 13
3

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");  
}
vmrob
  • 2,706
  • 23
  • 38
3

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. ;)

tzaman
  • 42,181
  • 9
  • 84
  • 108
  • 15
    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. – SuperElectric Nov 09 '10 at 20:33
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);
nulleight
  • 606
  • 5
  • 11
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...

cute_ptr
  • 853
  • 8
  • 11
3

Here is a solution for trim with regex

#include <string>
#include <regex>

string trim(string str){
    return regex_replace(str, regex("(^[ ]+)|([ ]+$)"),"");
}
Sadidul Islam
  • 586
  • 4
  • 8
3
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
Arty
  • 8,027
  • 3
  • 16
  • 26
  • This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post. - [From Review](/review/low-quality-posts/27905450) – k.s. Dec 22 '20 at 08:25
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);
}
Corwin Joy
  • 605
  • 6
  • 13
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.

Duncan
  • 1,484
  • 15
  • 20
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);
}
Kemin Zhou
  • 4,375
  • 1
  • 29
  • 47
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);
}
Brian W.
  • 339
  • 2
  • 5
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;
}
Jon Egerton
  • 36,729
  • 11
  • 90
  • 125
Brian
  • 11
  • 2
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;
}
Guillaume Jacquenot
  • 9,076
  • 5
  • 38
  • 47
elxala
  • 251
  • 2
  • 5
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); }
Jackt
  • 176
  • 1
  • 13
  • This is incorrect. You need to do invert the ltrim and rtrim for this to work. – Mathieu Westphal Oct 16 '19 at 05:02
  • 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. – Contango Feb 23 '20 at 13:56
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 );
}
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.

Bondolin
  • 2,241
  • 6
  • 25
  • 51
  • 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); }` – vmrob May 06 '14 at 02:46
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);
}
Floella
  • 1,121
  • 14
  • 37
0

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

user1438233
  • 983
  • 11
  • 27
  • Your code removes all the spaces even between non-spaces. That's not what "trim" is supposed to do – CITBL Jan 07 '19 at 16:32
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);
}
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;                                                                                                                                                                                                                               
}                                          
UnSat
  • 1,217
  • 2
  • 13
  • 25
0

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.

BullyWiiPlaza
  • 12,477
  • 7
  • 82
  • 129
0

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.

Anil Gupta
  • 129
  • 5
0

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)); };
Community
  • 1
  • 1
antb52
  • 31
  • 2
  • 6
0

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;
}
ragnarius
  • 5,018
  • 9
  • 42
  • 61
0

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);
}
Matthias
  • 11,313
  • 3
  • 43
  • 86
-1

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.

Vukieboo
  • 590
  • 8
  • 23
-7

It is so annoying that I

  • have to google it
  • find out that I have to use rocket science
  • that there is no simple trim/toupper function in string

For me this is the fastest way to solve it:

CString tmp(line.c_str());
tmp = tmp.Trim().MakeLower();
string buffer = tmp;

Ok, it is cool that I can use lambda ops, iterators, and all the stuff. But I only need to deal with a string instead of a character...

Slesa
  • 195
  • 1
  • 11
  • 2
    `CString` is a Windows-specific MFC/ATL abomination (which is arguably closer to "rocket science" than a few lines of standard C++ without 3rd-party dependencies). You cannot assume anyone can use it, or even wants to use it. – Christian Hackl Jul 08 '18 at 10:43
  • 1
    Of course I do not want to use it - but it is the shorter way to avoid lots of iterators to write down. I am not proud of these lines of code, but there should be an easier way to do it in the stdlib – Slesa Sep 25 '18 at 20:49
  • I agree that std::string lacks some obvious functionality. Simple stuff should not require multiple code lines. – ragnarius Aug 19 '20 at 22:21