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?
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, and there are no weird macros required.
To use stringstream class in the C++ program, we have to use the header <sstream>. For Example, the code to extract an integer from the string would be: string mystr(“2019”); int myInt; stringstream (mystr)>>myInt; Here we declare a string object with value “2019” and an int object “myInt”.
Stream class to operate on strings. Objects of this class use a string buffer that contains a sequence of characters. This sequence of characters can be accessed directly as a string object, using member str .
The StringStream class in C++ is derived from the iostream class. Similar to other stream-based classes, StringStream in C++ allows performing insertion, extraction, and other operations. It is commonly used in parsing inputs and converting strings to numbers, and vice-versa.
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;
...but this doesn't parse as a variable definition, let alone a definition and assignment.
The only ways to define and initialise a variable with a non-default value are (in a grammar sense - this isn't code):
type identifier(...args...); type identifier{...args...}; type identifier = ...expression...;
The last notation is equivalent to the first - i.e. type identifier(arg)
where arg is passed the ...expression...
.
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 int
s does what we want: it produces the int
result we then want to store in x
. You can think of the above as:
int x = (int)3 + (int)9; // notate the implicit types (int)12; // evaluate expression => value to assign int x((int)12); // construct a valid value
It works! But if you try that for stringstream
...
stringstream ss = "Number of people is " << numPeople; // BROKEN "Number of people is " << numPeople; // bitshift?!
...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]'
The problem is that the compiler's still trying to apply the bitwise shift operation, which only makes sense for numbers, because the overloads for <<
that we want to use require that any "X << Y" code has the left-hand part "X" be - or be implicitly convertible to - an ostream&
. A string literal can't be converted. At this point, the compiler is oblivious to the stringstream
to which the result of the expression will be passed.
It's a bit of a chicken-and-egg problem, because you 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 stringstream
s 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::ostringstream ss(static_cast<std::ostringstream&&>(std::ostringstream{} << "Number of people is " << numPeople)); ...or... auto&& ss = static_cast<std::ostringstream&&>(std::ostringstream{} << "Number of people is " << numPeople);
...but that's too hideous to contemplate.
Yes, you read that right. Depending on your sensibilities, you may feel a macro helps or is worse...
#define OSS(VALUES) \ static_cast<std::ostringstream&&>(std::ostringstream{} << VALUES) auto&& ss = OSS("Number of people is " << numPeople);
FWIW, you could also use the macro to create strings...
auto&& s = OSS("Number of people is " << numPeople).str();
...or create a dedicated macro...
#define STR(VALUES) \ static_cast<std::ostringstream&&>(std::ostringstream{} << VALUES).str() auto&& s = STR("Number of people is " << numPeople);
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, and there are no weird macros required.
C++11 introduced to_string()
overloads which are convenient if you have an integral value or two to concatentate with or into a string
:
auto&& 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::string
s will take time to deallocate during destruction.
Ideally, std::stringstream
would have a constructor accepting an arbitrary number of constructor arguments (A, B, C...)
to be formatted into the stringstream
as if by a subsequent << A << B << C...
. There are already constructors with arguments (e.g. (std::ios_base::openmode, const Allocator&)
), so we'd need a placeholder to distinguish such arguments from values we're trying to format into the stream, or a weirder workaround like requiring the values to be formatted into the stream be passed in as an initialiser list.
Still, it looks and feels very weird using strings with ,
instead of <<
:
std::stringstream ss{"n:", std::setw(4), std::hex, '\n');
And then if during code maintenance you find you need to move the streaming values to a point after construction, you'd need to change the separator. Breaking it out into two lines to start with - construction then streaming - simplifies that maintenance.
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 (unless the strings are short enough to fit inside the string object, a commonly provided std::string
technique called "Short String Optimisation" or SSO). There's also a deep copy of textual content. Construction-followed-by-streaming was a better approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With