28

I know there are several ways to do this in Java and C that are nice, but in C++ I can't seem to find a way to easily implement a string trimming function.

This is what I currently have:

string trim(string& str) { size_t first = str.find_first_not_of(' '); size_t last = str.find_last_not_of(' '); return str.substr(first, (last-first+1)); } 

but whenever I try and call

trim(myString); 

I get the compiler error

/tmp/ccZZKSEq.o: In function `song::Read(std::basic_ifstream<char, std::char_traits<char> >&, std::basic_ifstream<char, std::char_traits<char> >&, char const*, char const*)': song.cpp:(.text+0x31c): undefined reference to `song::trim(std::string&)' collect2: error: ld returned 1 exit status 

I am trying to find a simple and standard way of trimming leading and trailing whitespace from a string without it taking up 100 lines of code, and I tried using regex, but could not get that to work as well.

I also cannot use Boost.

2
  • Is song a namespace? Is it a class? Commented Sep 14, 2014 at 0:57
  • 4
    This question is not really to do with trimming, but with the linking error. Probably you get the same error regardless of the definition of trim Commented Sep 14, 2014 at 0:58

7 Answers 7

43

Your code is fine. What you are seeing is a linker issue.

If you put your code in a single file like this:

#include <iostream> #include <string> using namespace std; string trim(const string& str) { size_t first = str.find_first_not_of(' '); if (string::npos == first) { return str; } size_t last = str.find_last_not_of(' '); return str.substr(first, (last - first + 1)); } int main() { string s = "abc "; cout << trim(s); } 

then do g++ test.cc and run a.out, you will see it works.

You should check if the file that contains the trim function is included in the link stage of your compilation process.

Sign up to request clarification or add additional context in comments.

3 Comments

This gives crashes on empty string!
Add 'if (first == string::npos) return "";' to avoid crashing. Besides this it is nice and simple solution. I saw how people using boost (!!!) to implement such things.
string trim(string str) is more move-friendly function signature
29

Here is how you can do it:

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

And the supportive functions are implemeted as:

std::string & ltrim(std::string & str) { auto it2 = std::find_if( str.begin() , str.end() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } ); str.erase( str.begin() , it2); return str; } std::string & rtrim(std::string & str) { auto it1 = std::find_if( str.rbegin() , str.rend() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } ); str.erase( it1.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)); } 

Try this

3 Comments

+1 for introducing the locale which I miss from so many of the other answers around. Not neccessary the correct one as it depends on the application case, but still an important factor when dealing with strings.
Please add more details I could not figure out.
My bad. I confused it with the C function that takes int with values 0..255 or EOF whereas C++ locale isspace function takes normal char.
7

I think that substr() throws an exception if str only contains the whitespace.

I would modify it to the following code:

string trim(string& str) { size_t first = str.find_first_not_of(' '); if (first == std::string::npos) return ""; size_t last = str.find_last_not_of(' '); return str.substr(first, (last-first+1)); } 

2 Comments

This doesn't work for tabs or other kinds of whitespace besides spaces.
@jocull - you are right, but i think its obvious what to do to specify more whitespaces, find_first_not_of(" \t\n\r\v\f")
4

Using a regex

#include <regex> #include <string> string trim(string s) { regex e("^\\s+|\\s+$"); // remove leading and trailing spaces return regex_replace(s, e, ""); } // if you prefer the namespaced version std::string trim(std::string s) { std::regex e("^\\s+|\\s+$"); // remove leading and trailing spaces return std::regex_replace(s, e, ""); } 

Credit to: https://www.regular-expressions.info/examples.html for the regex

As @o_oTurtle mentioned - regexes are very slow.

An alternate approach, which includes additional whitespace characters:

std::string trim(const std::string& str, const std::string REMOVE = " \n\r\t") { size_t first = str.find_first_not_of(REMOVE); if (std::string::npos == first) { return str; } size_t last = str.find_last_not_of(REMOVE); return str.substr(first, (last - first + 1)); } 

1 Comment

Regex is dramatically slow in C++ and I always prevent using them...
0
#include <vector> #include <numeric> #include <sstream> #include <iterator> void Trim(std::string& inputString) { std::istringstream stringStream(inputString); std::vector<std::string> tokens((std::istream_iterator<std::string>(stringStream)), std::istream_iterator<std::string>()); inputString = std::accumulate(std::next(tokens.begin()), tokens.end(), tokens[0], // start with first element [](std::string a, std::string b) { return a + " " + b; }); } 

Comments

0

In addition to answer of @gjha:

inline std::string ltrim_copy(const std::string& str) { auto it = std::find_if(str.cbegin(), str.cend(), [](char ch) { return !std::isspace<char>(ch, std::locale::classic()); }); return std::string(it, str.cend()); } inline std::string rtrim_copy(const std::string& str) { auto it = std::find_if(str.crbegin(), str.crend(), [](char ch) { return !std::isspace<char>(ch, std::locale::classic()); }); return it == str.crend() ? std::string() : std::string(str.cbegin(), ++it.base()); } inline std::string trim_copy(const std::string& str) { auto it1 = std::find_if(str.cbegin(), str.cend(), [](char ch) { return !std::isspace<char>(ch, std::locale::classic()); }); if (it1 == str.cend()) { return std::string(); } auto it2 = std::find_if(str.crbegin(), str.crend(), [](char ch) { return !std::isspace<char>(ch, std::locale::classic()); }); return it2 == str.crend() ? std::string(it1, str.cend()) : std::string(it1, ++it2.base()); } 

Comments

0

A solution with tests

It is somewhat surprising that none of the answers here provide a test function to demonstrate how trim behaves in corner cases. The empty string and strings composed entirely of spaces can both be troublesome.

Here is such a function:

#include <iomanip> #include <iostream> #include <string> void test(std::string const& s) { auto const quote{ '\"' }; auto const q{ quote + s + quote }; auto const t{ quote + trim(s) + quote }; std::streamsize const w{ 6 }; std::cout << std::left << "s = " << std::setw(w) << q << " : trim(s) = " << std::setw(w) << t << '\n'; } int main() { for (std::string s : {"", " ", " ", "a", " a", "a ", " a "}) test(s); } 

Testing the accepted solution

When I ran this on 2023-Aug-05 against the accepted answer, I was disappointed with how it treated strings composed entirely of spaces. I expected them to be trimmed to the empty string. Instead, they were returned unchanged. The if-statement is the reason why.

// Accepted solution by @Anthony Kong. Copied on 2023-Aug-05. using namespace std; string trim(const string& str) { size_t first = str.find_first_not_of(' '); if (string::npos == first) { return str; } size_t last = str.find_last_not_of(' '); return str.substr(first, (last - first + 1)); } 

Here is the output from my test:

s = "" : trim(s) = "" s = " " : trim(s) = " " s = " " : trim(s) = " " s = "a" : trim(s) = "a" s = " a" : trim(s) = "a" s = "a " : trim(s) = "a" s = " a " : trim(s) = "a" 

Testing the "new and improved" version of trim

To make it work the way I wanted, I changed the if-statement.

While I was at it, I got rid of using namespace std;, and changed the parameter name to s. You know, because I could.

// "New and improved" version of trim. Lol. std::string trim(std::string const& s) { auto const first{ s.find_first_not_of(' ') }; if (first == std::string::npos) return {}; auto const last{ s.find_last_not_of(' ') }; return s.substr(first, (last - first + 1)); } 

Now the test routine produces the output I like:

s = "" : trim(s) = "" s = " " : trim(s) = "" s = " " : trim(s) = "" s = "a" : trim(s) = "a" s = " a" : trim(s) = "a" s = "a " : trim(s) = "a" s = " a " : trim(s) = "a" 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.