C++ – Why does std::ends cause string comparison to fail

boostcstl

I spent about 4 hours yesterday trying to fix this issue in my code. I simplified the problem to the example below.

The idea is to store a string in a stringstream ending with std::ends, then retrieve it later and compare it to the original string.

#include <sstream>
#include <iostream>
#include <string>

int main( int argc, char** argv )
{
    const std::string HELLO( "hello" );

    std::stringstream testStream;

    testStream << HELLO << std::ends;

    std::string hi = testStream.str();

    if( HELLO == hi )
    {
        std::cout << HELLO << "==" << hi << std::endl;
    }

    return 0;
}

As you can probably guess, the above code when executed will not print anything out.

Although, if printed out, or looked at in the debugger (VS2005), HELLO and hi look identical, their .length() in fact differs by 1. That's what I am guessing is causing the "==" operator to fail.

My question is why. I do not understand why std::ends is an invisible character added to string hi, making hi and HELLO different lengths even though they have identical content. Moreover, this invisible character does not get trimmed with boost trim. However, if you use strcmp to compare .c_str() of the two strings, the comparison works correctly.

The reason I used std::ends in the first place is because I've had issues in the past with stringstream retaining garbage data at the end of the stream. std::ends solved that for me.

Best Answer

std::ends inserts a null character into the stream. Getting the content as a std::string will retain that null character and create a string with that null character at the respective positions.

So indeed a std::string can contain embedded null characters. The following std::string contents are different:

ABC
ABC\0

A binary zero is not whitespace. But it's also not printable, so you won't see it (unless your terminal displays it specially).

Comparing using strcmp will interpret the content of a std::string as a C string when you pass .c_str(). It will say

Hmm, characters before the first \0 (terminating null character) are ABC, so i take it the string is ABC

And thus, it will not see any difference between the two above. You are probably having this issue:

std::stringstream s;
s << "hello";
s.seekp(0);
s << "b";
assert(s.str() == "b"); // will fail!

The assert will fail, because the sequence that the stringstream uses is still the old one that contains "hello". What you did is just overwriting the first character. You want to do this:

std::stringstream s;
s << "hello";
s.str(""); // reset the sequence
s << "b";
assert(s.str() == "b"); // will succeed!

Also read this answer: How to reuse an ostringstream