views:

626

answers:

3

Consider the following function:

void f(const char* str);

Suppose I want to generate a string using stringstream and pass it to this function. If I want to do it in one statement, I might try:

f((std::ostringstream() << "Value: " << 5).str().c_str()); // error

This gives an error: 'str()' is not a member of 'basic_ostream'. OK, so operator<< is returning ostream instead of ostringstream - how about casting it back to an ostringstream?

1) Is this cast safe?

f(static_cast<std::ostringstream&>(std::ostringstream() << "Value: " << 5).str().c_str()); // incorrect output

Now with this, it turns out for the operator<<("Value: ") call, it's actually calling ostream's operator<<(void*) and printing a hex address. This is wrong, I want the text.

2) Why does operator<< on the temporary std::ostringstream() call the ostream operator? Surely the temporary has a type of 'ostringstream' not 'ostream'?

I can cast the temporary to force the correct operator call too!

f(static_cast<std::ostringstream&>(static_cast<std::ostringstream&>(std::ostringstream()) << "Value: " << 5).str().c_str());

This appears to work and passes "Value: 5" to f().

3) Am I relying on undefined behavior now? The casts look unusual.


I'm aware the best alternative is something like this:

std::ostringstream ss;
ss << "Value: " << 5;
f(ss.str().c_str());

...but I'm interested in the behavior of doing it in one line. Suppose someone wanted to make a (dubious) macro:

#define make_temporary_cstr(x) (static_cast<std::ostringstream&>(static_cast<std::ostringstream&>(std::ostringstream()) << x).str().c_str())

// ...

f(make_temporary_cstr("Value: " << 5));

Would this function as expected?

+2  A: 

A temporary cannot be passed as a non-const reference to a function, that's why it does not find the correct streaming operator and instead takes the one with void* argument (it is a member function and thus calling it on a temporary is OK).

What comes to getting around the limitations by casting, I have a feeling that it is in fact UB, but I cannot say for sure. Someone else will surely quote the standard.

Tronic
I'm not passing the temporary ostringstream to f() - I'm only passing the result of c_str(), which is const (if it matters). Besides, even so, what about that means it picks the void* argument? What's different about it that allows it to be picked but not the const char* argument?
AshleysBrain
Tronic
+4  A: 

You cannot cast the temporary stream to std::ostringstream&. It is ill-formed (the compiler must tell you that it is wrong). The following can do it, though:

f(static_cast<std::ostringstream&>(
  std::ostringstream().seekp(0) << "Value: " << 5).str().c_str());

That of course is ugly. But it shows how it can work. seekp is a member function returning a std::ostream&. Would probably better to write this generally

template<typename T>
struct lval { T t; T &getlval() { return t; } };

f(static_cast<std::ostringstream&>(
  lval<std::ostringstream>().getlval() << "Value: " << 5).str().c_str());

The reason that without anything it takes the void*, is because that operator<< is a member-function. The operator<< that takes a char const* is not.

Johannes Schaub - litb
Your snippet with seekp(0) returns an empty string for me - but the lval template appears to work correctly. That relies fully on defined behavior then, and would be portable?
AshleysBrain
@Ashley, sure it's all defined.
Johannes Schaub - litb
Marcus Lindblom
@Macus, yep that wouldn't work. In free operators, template argument deduction would fail. And for member operators, those wouldn't be found in the first place. The conversion function isn't taken.
Johannes Schaub - litb
A: 

If you like one_lined statements you can write:

// void f(const char* str); 
f(static_cast<ostringstream*>(&(ostringstream() << "Value: " << 5))->str());

However you should prefer easier to maintain code as:

template <typename V>
  string NumberValue(V val)
  {
     ostringstream ss;
     ss << "Value: " << val;
     return ss.str();
  }
f(NumberValue(5));

cheers, AR

Alain Rist
The one-liner you have suggested is wrong and would not work for all cases.
missingfaktor