How to initialize a std::stringstream?

Dragons_Lair5 picture Dragons_Lair5 · Feb 21, 2014 · Viewed 27.3k times · Source

I need to concatenate a string with integers. To do that I'm using stringstream in the following way:

int numPeople = 10;
stringstream ss;
ss << "Number of people is " << numPeople;

And that worked. But I was trying to do it in the way below:

int numPeople = 10;
stringstream ss << "Number of people is " << numPeople;

And I was getting the following error: "expected initializer before '<<' token"

Why was I getting this error? Why can't I assign the stringstream value at the same time I declare it?

Answer

Tony Delroy picture Tony Delroy · Feb 21, 2014
stringstream ss << "Number of people is " << numPeople;

Why can't I assign the stringstream value at the same time I declare it?

This is similar to hoping this would work:

int x + 3 + 9;

The problem

When you're both defining and giving a value to an object, C++ lets you call the constructor, which requires a comma-separated list of expressions. For convenience the Type variable = value; notation is elided to call Type variable(value);, but only works for a single value.

For int, you can easily correct the code:

int x = 3 + 9;

...and it works because "3 + 9" can be evaluated independently first to give a sane value to store in x. The compiler's behaviour for operator + on ints does what we want: it produces the int result we then want to store in x. But if you try that for stringstream...

stringstream ss = "Number of people is " << numPeople;  // BROKEN

...it won't work, because "Number of people is " << numPeople needs to be evaluated first but is illegal - you'll get an error like "error C2296: '<<' : illegal, left operand has type 'const char [20]'" - it won't give a useful value for the stringstream constructor. The problem is that the compiler's still trying to apply the bitwise shift operator, which only makes sense for numbers, as the overloads for << that we'd want to apply require a left-hand argument of type ostream&. C++ requires the value being evaluated on the right of = to be evaluated independently of the assignment that's eventually done with the resultant value, and at that point the type of the variable being constructed is not relevant to the way evaluation is attempted on the expression being assigned.

A solution

It's a bit of a chicken-and-egg problem here, because you kind of need to combine the right-hand values you want in the stringstream to call the stringstream's constructor, but for that you need... a stringstream. You can actually pull that off with a temporary stringstream:

static_cast<std::ostringstream&&>(std::ostringstream() << "Number of people is " << numPeople)

The cast is unfortunately needed because the operator<< overloads handle stringstreams via references to their ostream base class, returning an ostream&, so you need to cast back to the stringstream type manually, so you can then invoke the std::stringstream move constructor...

The complete one-liner construction is then...

std::stringstream ss(static_cast<std::ostringstream&&>(std::ostringstream() << "Number of people is " << numPeople));

...but that's too hideous to contemplate.

Making the solution (arguably) less hideous

Depending on your sensibilities, you may feel a macro helps or is worse...

#define OSS(VALUES) \
    static_cast<std::ostringstream&&>(std::ostringstream() << VALUES)

std::stringstream ss(OSS("Number of people is " << numPeople));

FWIW, you could also use the macro to create strings...

std::string s(OSS("Number of people is " << numPeople).str()); 

An (arguably) better practice

Just create the stringstream - optionally providing a single string to the constructor - then use operator<< in a second statement:

std::stringstream ss;
ss << "Number of people is " << numPeople;

This is much easier to read. With move construction, after optimisation there's likely no performance reasons for preferring two statements.

An alternative

C++11 introduced to_string() overloads which are convenient if you have an integral value or two to concatentate with or into a string:

std::string s = "Number of people is " + std::to_string(numPeople);

This may be inefficient though (check your compiler(s) optimisation abilities if you care): each std::to_string() is likely to dynamically allocate a buffer for an independent std::string instance, then the individual concatenations may involve extra copying of text, and the original dynamically-allocated buffers may need to be enlarged, then most of those temporary std::strings will take time to deallocate during destruction.

It was worse in C++03

C++03 lacked move constructors, so it was necessary to use the std::ostringstream::str() member function on the temporary to get an extra deep-copy of the std::string with which to construct the named stringsteam...

stringstream ss(static_cast<std::ostringstream&>(std::ostringstream() << "Number of people is " << numPeople).str());

With this C++03 code, there's a likelihood of duplicate dynamic memory allocations and a copy of content, so construction-followed-by-streaming was a better bet.