Remove excessive whitespace from string Is that standards-compliant?

#include &lt;string&gt;
#include &lt;algorithm&gt;
#include &lt;iostream&gt;
#include &lt;cctype&gt;

std::string remove_excessive_ws(std::string const &amp;str)
{
    std::string result{ str };
    auto end{ std::remove_if(std::begin(result), std::end(result),
                             [](std::string::value_type const &amp;ch) {
                                  return std::isspace(static_cast&lt;unsigned&gt;(ch)) &amp;&amp;
                                         (std::isspace(static_cast&lt;unsigned&gt;((&amp;ch)[1])) || (&amp;ch)[1] == '\0');
                             }
              )
    };
    result.erase(end, std::end(result));
    return result;
}

int main()
{
    char const *foo{ "Hello              World!       " };
    std::cout &lt;&lt; '\"' &lt;&lt; remove_excessive_ws(foo) &lt;&lt; "\"\n";
}


I'm unsure whether I should be accessing (&amp;ch)[1]. I think it should be legal, but I am not sure. After all, std::remove_if() could copy the character and pass it to the function, so the pointer (&amp;ch) + 1 might not be valid.
Define the problem

It's not clear from the description what's considered "excessive" whitespace.  From experimenting, it seems that the idea is to collapse multiple whitespace to a single whitespace character, except at the end of the string, where whitespace is to be completely removed.  (Whitespace at the beginning of string seems to treated the same as inner whitespace).  It's also not clear which whitespace character should be kept - the example code keeps the last one, but is that a requirement, or just an implementation choice?

Missing include

std::begin() and std::end() are declared in &lt;iterator&gt;.  However, there's no reason not to use the begin() and end() member functions of std::string here, as we're not operating on generic values.

When an argument is to be copied, pass by value

We don't need to copy str into result:

std::string remove_excessive_ws(std::string str)


Bug

Like all the &lt;cctype&gt; functions, std::isspace() requires that its argument be either EOF or representable as unsigned char.  Converting a (possibly signed) char direct to unsigned int can sign-extend to an out-of-range value.  We need to convert char to unsigned char before widening to unsigned int:

static_cast&lt;unsigned char&gt;(ch)


Bug

Prior to C++11, accessing the character after the end of the string is undefined behaviour (C++11 requires an extra null to follow the string data).  Thankfully, it's easy to avoid this bug by simply remembering whether the last character seen was a space.

Here's a C++11 version:

#include &lt;algorithm&gt;
#include &lt;cctype&gt;
#include &lt;string&gt;
#include &lt;utility&gt;

std::string remove_excessive_ws(std::string str)
{
    bool seen_space = false;
    auto end{ std::remove_if(str.begin(), str.end(),
                             [&amp;seen_space](unsigned char ch) {
                                 bool is_space = std::isspace(ch);
                                 std::swap(seen_space, is_space);
                                 return seen_space &amp;&amp; is_space;
                             })};
    // adjust end to remove end whitespace
    if (end != str.begin() &amp;&amp; std::isspace(static_cast&lt;unsigned char&gt;(end[-1]))) {
        --end;
    }
    str.erase(end, str.end());
    return str;
}


We might want to move seen_space into the lambda expression in later C++ versions that allow that.

This also is more readable, as we can perform the widening to unsigned int when calling the lambda, rather than having to write a cast.

Style-wise, I'd normally prefer to name the lambda, and keep the erase-remove call on a single line so that the idiom is obvious:

// Assuming C++17 now
std::string remove_excessive_ws(std::string s)
{
    auto is_doubled_space =
        [seen_space=false](unsigned char c) mutable {
            return std::exchange(seen_space, std::isspace(c))
                &amp;&amp; seen_space;
        };
    s.erase(std::remove_if(s.begin(), s.end(), is_doubled_space), s.end());
    // remove trailing whitespace
    if (!s.empty() &amp;&amp; std::isspace(static_cast&lt;unsigned char&gt;(s.back()))) {
        s.pop_back();
    }
    // convert all whitespace into ordinary space character
    std::replace_if(s.begin(), s.end(),
                    [](unsigned char c) { return std::isspace(c); }, ' ');
    return s;
}