5

I have been using the following code to clear std::stringstream:

m.swap(std::stringstream());

Code probably taken from this SO thread. Recently I compiled my code in Visual Studio 2013 and I received the following warning:

warning C4239: nonstandard extension used : 'argument' : conversion from 'std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>>' to 'std::basic_stringstream<char,std::char_traits<char>,std::allocator<char>> &'
1>          A non-const reference may only be bound to an lvalue

I did not receive any warning in earlier versions of Visual Studio using /W4. I recall I had trouble reliably clearing stringstreams in earlier versions which is why I ended up using that code in the first place.

So is it portable to clear a stringstream that way, and if not could you explain why not? Also is there a portable way to clear a stringstream? Thanks

Edit: Here is the code that was requested. Compile it using /W4 to see the warning.

int _tmain(int argc, _TCHAR* argv[])
{
    std::stringstream m;
    m.swap(std::stringstream());

    return 0;
}
Community
  • 1
  • 1
user2672807
  • 1
  • 2
  • 7
  • 20

3 Answers3

4

Here's the signature of std::stringstream::swap():

void swap( basic_stringstream& other );

As you can see, swap() takes an lvalue-reference to its argument. An rvalue or temporary cannot bind to an lvalue-reference as your compiler is correctly telling you. Some compilers support non-standard extensions that allow you to do this, but I would advise that you stay within the bounds of the standard, it's much safer.

However, while you may not be able to bind the temporary to the lvalue-reference, you can transform the temporary into an lvalue using this std::skipws trick:

m.swap(static_cast<std::stringstream&>(stringstream{} >> std::skipws));

or even

std::stringstream{}.swap(m):

In the first example, the function operator>>() returns a reference to the stream, which is safe to use with swap() because the reference that holds the argument is destroyed at the end of the function.

David G
  • 94,763
  • 41
  • 167
  • 253
  • @vsoftco How exactly doesn't it work? Are you receiving an error? If so, what is it? – David G May 17 '14 at 21:49
  • Yes, `error: non-const lvalue reference to type 'std::__1::basic_stringstream, std::__1::allocator >' cannot bind to a value of unrelated type 'std::__1::basic_istream'` – vsoftco May 17 '14 at 21:51
  • this is the code `#include #include using namespace std; int main() { stringstream m; m.swap(stringstream{} >> skipws); } ` – vsoftco May 17 '14 at 21:51
  • It works if you use a move function to add a reference, as you mentioned before on my problem with stringstreams. – vsoftco May 17 '14 at 21:52
  • @vsoftco Oh I see. You have to cast it to a `std::stringstream&` to get that to work. – David G May 17 '14 at 21:54
  • @vsoftco Yup, that's another solution. Thanks for reminding me. `:D` – David G May 17 '14 at 22:05
  • Although I am getting a g++ 4.8 error that `error: 'std::stringstream' has no member named 'swap'`, even though it looks like `stringstream` has a swap member, I am now really confused about `g++` implementation of the standard. It should work according to http://www.cplusplus.com/reference/sstream/stringstream/swap/ So basically I cannot swap 2 stringstreams, like `m.swap(n)`, where `stringstream m,n;` – vsoftco May 17 '14 at 22:05
  • @vsoftco Turns out g++ doesn't support `std::stringstream::swap()`. I was trying this code on clang where it happily compiled. Then again, clang is known for being more up-to-date than g++. `swap()` is a newly-added C++11 function. – David G May 17 '14 at 22:08
  • Well this is strange, just tried it with `g++4.9` and still doesn't work, although they claim that `g++4.9` is fully C++11 ready... `clang++` indeed works. – vsoftco May 17 '14 at 22:10
  • @0x499602D2 Can you tell me why you need the `static_cast`? As I understood before, the `>>` operator returns a reference, why then do you need to cast it again? Thanks! – vsoftco May 17 '14 at 22:15
  • @vsoftco Because `operator>>()` returns *`std::istream&`* not `std::stringstream&`. We need to downcast it to a reference to the derived class. – David G May 17 '14 at 22:18
1

The std::swap function takes two non-const parameters to swap. This is because both parameters are changed when the function is called. Similarly, the std::stringstream::swap(...) function, takes a single non-const parameter to swap with.

For example:

std::vector<int> vec1, vec2;
std::swap(vec1, vec2);

In the above sample, both vec1 and vec2 are altered. When you try to do this...

m.swap(std::stringstream());

... you're trying to assign the contents of the temporary std::stringstream() with the contents of m, which to a compiler looks like a bug, even if your intention was simply to clear the contents of m.

To remove the warning, simply declare a temporary std::stringstream before performing the swap. Like this:

std::stringstream temp;
m.swap(temp);
Karl Nicoll
  • 16,090
  • 3
  • 51
  • 65
  • 3
    Alternately, make the temporary first. `std::stringstream().swap(m);` – Billy ONeal May 17 '14 at 21:21
  • @BillyONeal - Indeed you could also do that! – Karl Nicoll May 17 '14 at 21:22
  • Had somewhat a similar issue... see the comments about the `lvalue` template http://stackoverflow.com/questions/23714445/passing-temporary-istringstream-object-to-istream-iteratorstring/23714506#23714506 – vsoftco May 17 '14 at 21:36
  • 1
    Basically you are bounding a temporary to a non-const lvalue reference, which is not possible. You have to create a reference to the temporary, `template T& ref_from_temp(T&& x) {return x;}` using move semantics in C++11, then replace `m.swap(stringstream());` by `m.swap(ref_from_temp(stringstream()));` – vsoftco May 17 '14 at 21:43
  • @vsoftco Thanks for the suggestion. I decided to go with Billy's one-liner since even though it's not strictly C++03 compatible it works with Visual Studio 2010. – user2672807 May 17 '14 at 21:48
  • @vsoftco: No, you are not binding a temporary to a non-const lvalue reference there. The only thing bound to a non-const lvalue reference is `m`, which is perfectly fine. – Billy ONeal May 17 '14 at 23:34
  • @user: It is valid C++03. – Billy ONeal May 17 '14 at 23:42
  • @BillyONeal, in `m.swap(std::stringstream());`, don't you try to invoke `swap` with a temp reference? http://www.cplusplus.com/reference/sstream/stringstream/swap/ At least that's my understanding. – vsoftco May 18 '14 at 05:46
  • @vsoftco: Yes, that would be bad. That's why I said `std::stringstream().swap(m)`. The temporary becomes `this` and `m` is bound to the non-const lvalue reference. Swap doesn't care who is the left hand side and who is the right hand side. – Billy ONeal May 18 '14 at 17:56
1

That use of swap is not allowed by the Standard. The declaration is

template <typename CharT, typename Traits, typename Allocator>
void std::basic_stringstream<CharT, Traits, Allocator>::swap(
    basic_stringstream& other);

which implies that both stringstream objects will be modified. But the expression std::stringstream() is an rvalue (specifically, a temporary), and C++ does not allow binding non-const lvalue references to temporaries, since modifying a temporary is not often the real intent.

The easy solution is:

m = std::stringstream();

But if you need to support compilers without C++11 support, neither will work, since in the C++03 Standard, stringstream has no swap function and no public operator=. In that case, you would need to go with:

m.clear();
m.str(std::string());
aschepler
  • 70,891
  • 9
  • 107
  • 161
  • How do you explain the fact that the example posted compiles? – 101010 May 17 '14 at 21:31
  • The problem with .str() is apparently it didn't always work for me to clear the stream and I don't remember why. – user2672807 May 17 '14 at 21:37
  • `m.clear()` might also be necessary to do a full reset. And maybe other things. This is difficult using only C++03 functions. – aschepler May 17 '14 at 21:44
  • Ok thanks for the extra information, I appreciate it. I decided to go with `std::stringstream().swap(m);` since even though it's not C++03 it works in Visual Studio 2010 and 2013. I would really like better portability but I don't want to run the risk of introducing a bug if more than clear() and str(std::string()) is required. – user2672807 May 17 '14 at 21:52