Trim string implementation in C++

I was working with some strings and I wondered how you can trim a string in C++. Having iterators and so many algorithms (too many?) in the Standard Library gives a lot of flexibility, and some tasks were left out of the standards.

The flexibility of C++ feels like morally pushing you to also write flexible code which can cover a lot of needs. Most probably some things could be improved to my implementation of string trimming.

The functions are ltrim (erase from left), rtrim (erase from right) and trim (erase from left and right). All three take a reference to a string (the input string is modified) and a predicate function to match the characters you want to erase (std::isspace as default):

using Predicate = std::function<int(int)>;

static inline void ltrim(std::string &str, Predicate const &pred = isspace);

static inline void rtrim(std::string &str, Predicate const &pred = isspace);

static inline void trim(std::string &str, Predicate const &pred = isspace);

 

Left trim finds the first character which is NOT evaluated by the predicate as true, then erases the string from the beginning to the found character.

static inline void ltrim(std::string &str, Predicate const &pred) {
    str.erase(str.begin(), std::find_if_not(str.begin(), str.end(), pred));
}

 

Right trim uses the reverse iterator to find the position in the input string where to start erasing, and erases until the end. The base iterator must be taken from the reverse iterator before passing it the to the erase method.

static inline void rtrim(std::string &str, Predicate const &pred) {
    str.erase((std::find_if_not(str.rbegin(), str.rend(), pred)).base(), str.end());
}

 

Trim just calls both ltrim and rtrim.

static inline void trim(std::string &str, Predicate const &pred) {
    ltrim(str, pred);
    rtrim(str, pred);
}

 

A common case is to trim a character, so I’ve overloaded the three functions to accept a char instead of the predicate. A predicate lambda is generated to test the equality of each string character with the character being passed. Then the original functions are called with the generated predicate. See the full code with tests below.

#include <functional>
#include <string>
#include <algorithm>
#include <vector>
#include <tuple>
#include <stdexcept>

using Predicate = std::function<int(int)>;

/**
 * ltrim removes from the left of a string the chars matching the predicate
 *
 * @param string str
 * @param Predicate pred
 */
static inline void ltrim(std::string &str, Predicate const &pred = isspace);

/**
 * rtrim removes from the right of a string the chars matching the predicate
 *
 * @param string str
 * @param Predicate pred
 */
static inline void rtrim(std::string &str, Predicate const &pred = isspace);

/**
 * trim removes from the left and the right of a string the chars matching the predicate
 *
 * @param string str
 * @param Predicate pred
 */
static inline void trim(std::string &str, Predicate const &pred = isspace);

/**
 * equal_ch generates a predicate lambda to check equality to char c
 *
 * @param char c
 * @return Predicate
 */
static inline Predicate equal_ch(char c);

/**
 * ltrim removes from the left of a string the chars equal to param c
 *
 * @param string str
 * @param char c
 */
static inline void ltrim(std::string &str, char c);

/**
 * rtrim removes from the right of a string the chars equal to param c
 *
 * @param string str
 * @param char c
 */
static inline void rtrim(std::string &str, char c);

/**
 * trim removes from the left and the right of a string the chars equal to param c
 *
 * @param string str
 * @param char c
 */
static inline void trim(std::string &str, char c);

int main() {
    using test = std::tuple<std::string, std::string, std::string (*)(std::string)>;
    const std::vector<test> tests = {
            {
                    {
                            "  \t  string to trim \t \n \r ",
                            "string to trim",
                            [](std::string in) {
                                trim(in);
                                return in;
                            }
                    },
                    {
                            "abc TEXT def",
                            " TEXT ",
                            [](std::string in) {
                                trim(in, islower);
                                return in;
                            }
                    },
                    {
                            "  \t  string to trim \t \n \r ",
                            "string to trim \t \n \r ",
                            [](std::string in) {
                                ltrim(in);
                                return in;
                            }
                    },
                    {
                            "  \t  string to trim \t \n \r ",
                            "  \t  string to trim",
                            [](std::string in) {
                                rtrim(in);
                                return in;
                            }
                    },
                    {
                            "xxtrimxx",
                            "trim",
                            [](std::string in) {
                                trim(in, 'x');
                                return in;
                            }
                    },
                    {
                            "xxtrimxx",
                            "trimxx",
                            [](std::string in) {
                                ltrim(in, 'x');
                                return in;
                            }
                    },
                    {
                            "yytrimyy",
                            "yytrim",
                            [](std::string in) {
                                rtrim(in, 'y');
                                return in;
                            }
                    },
            }
    };

    for (auto &test : tests) {
        auto &input = std::get<0>(test);
        auto &expected = std::get<1>(test);
        auto &trim_func = std::get<2>(test);
        auto result = trim_func(input);

        if (result != expected) {
            char message[100];
            sprintf(message, R"(actual: "%s"; expected: "%s")", result.c_str(), expected.c_str());
            throw std::runtime_error(message);
        }
    }
}

static inline void ltrim(std::string &str, Predicate const &pred) {
    str.erase(str.begin(), std::find_if_not(str.begin(), str.end(), pred));
}

static inline void rtrim(std::string &str, Predicate const &pred) {
    str.erase((std::find_if_not(str.rbegin(), str.rend(), pred)).base(), str.end());
}

static inline void trim(std::string &str, Predicate const &pred) {
    ltrim(str, pred);
    rtrim(str, pred);
}

static inline Predicate equal_ch(char c) {
    return (int i) -> int { return i == c; };
}

static inline void ltrim(std::string &str, char c) {
    ltrim(str, equal_ch(c));
}

static inline void rtrim(std::string &str, char c) {
    rtrim(str, equal_ch(c));
}

static inline void trim(std::string &str, char c) {
    ltrim(str, equal_ch(c));
    rtrim(str, equal_ch(c));
}

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.