Search code examples
c++stdstring

What exactly is assigned by the `=` assigment to the new std::string?


I want to write a method that should trim a std::string's ongoing and trailing white spaces. For example, if the method gets " HELLO WORLD ", it should return "HELLO WORLD". Note, that between the return string is a white space, that is important.

This is the signature of my method:

std::string MyHandler::Trim(const std::string& toTrim)

My approach was to copy the 'toTrim' parameter into a non constant copy.

std::string toTrimCopy = toTrim; 

Now I want to get a non constant iterator and erase within a for loop any whitespaces from beginning and the end, while the iterators value is a white space.

for (std::string::iterator it = toTrim.begin(); *it == ' '; it++)
{
    toTrimCopy.erase(it);
}

for (std::string::iterator it = toTrim.end();
     *it == ' ';
     it--)
{
    toTrimCopy.erase(it);
}

This results in the compiler error:

StringHandling.C:60:49: error: conversion from ‘std::basic_string<char>::const_iterator {aka __gnu_cxx::__normal_iterator<const char*, std::basic_string<char> >}’ to non-scalar type ‘std::basic_string<char>::iterator {aka __gnu_cxx::__normal_iterator<char*, std::basic_string<char> >}’ requested

I come from Java and I learn C++ for 3 weeks now. So dont judge me. I suspect, that the = assignment assigns constant char pointer to my new string, so that my copy is implicit a constant value. But I do not exactly know.

By the way, this approach throws an exception as well:

    std::string toTrimCopy = "";
    std::strcpy(toTrimCopy, toTrim);

He says, that he cant convert a string to a char pointer.


Solution

  • It's undefined behaviour to pass an iterator from toTrim to methods of toTrimCopy, so you are lucky that the type mismatch caught it.

    std::string::begin has two overloads:

     std::string::iterator std::string::begin();
     std::string::const_iterator std::string::begin() const;
    

    The constness of the string participates in overload resolution. You can't initialise an iterator from a const_iterator, because that would allow you to modify the underlying object through that iterator.

    I would change your function to

    namespace MyHandlerNS { // optional, could be in global namespace
        std::string Trim(std::string toTrim) {
            for (auto it = toTrim.begin(); it != toTrim.end() && *it == ' ';) {
                it = toTrim.erase(it);
            }
    
            for (auto it = toTrim.rbegin(); it != toTrim.rend() && *it == ' ';) {
                it = toTrim.erase(it.base());
            }
    
            return toTrim;
        }
    }
    

    Or get rid of the loops entirely with standard algorithms

    #include <algorithm>
    
    namespace MyHandlerNS { // optional, could be in global namespace
        std::string Trim(std::string toTrim) {
            auto isSpace = [](char c){ return c == ' '; };
    
            auto it = std::find_if_not(toTrim.begin(), toTrim.end(), isSpace);
            toTrim.erase(toTrim.begin(), it);
    
            it = std::find_if_not(toTrim.rbegin(), toTrim.rend(), isSpace).base();
            toTrim.erase(it, toTrim.end());
    
            return toTrim;
        }
    }