Re: How to build a string on the fly, using std::ostringstream? [Reposting]
Thanks to all of you for your replies so far!
Martin York wrote:
Is your problem that you have other objects of non string types that
you want to append onto a string. In this case you could use
boost::lexical_cast()
Yes, I would like to append any number of objects onto a string. And
you're right, I could indeed use boost::lexical_cast. But in my case, I
think it's too expensive and too verbose to do so:
std::string s = boost::lexical_cast<std::string>(obj1) +
boost::lexical_cast<std::string>(obj2) +
boost::lexical_cast<std::string>(obj3) +
boost::lexical_cast<std::string>(objN);
Peter Jones wrote:
Why would you want to create a string from an ostringstream
without having to use a named ostringstream?
Actually I was thinking of defining a macro, as follows:
#define STRING(arg) \
static_cast<std::ostringstream&>( \
Wrapper<std::ostringstream>().get() << arg).str()
It would allow doing:
std::string s1 = STRING("i = " << i);
std::string s2 = STRING(obj1 << obj2 << obj3 << objN);
Clearly such a macro definition would need an unnamed temporary to do
the streaming.
I wrote:
std::string s = (std::ostringstream() << "i = " << i).str();
Of course it didn't compile, because a temporary (std::ostringstream())
cannot be passed as non-const reference to a function (operator<<).
Vidar Hasfjord replied:
Well, that is not the case on the implementations I use (VC7.1/VC9.0).
The << operator is implemented as a member of ostream; which does bind
to temporaries; so this works fine:
ostringstream () << "i = " << i; // compiles
Oops, an oversight on my side! Both www.dinkumware.com/exam and
www.comeaucomputing.com/tryitout do indeed accept inserting a literal
string onto a temporary ostringstream. So apparently those
implementations have defined this particular operator<< as a member
function!
I don't know if the standard mandates this or not though.
The Draft <www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2521.pdf>,
[ostream] specifies operator<< for C-style strings as a non-member:
template<class charT, class traits>
basic_ostream<charT,traits>& operator<<(basic_ostream<charT,traits>&,
const char*);
Interestingly, the Draft also provides a new set of overloads, having an
rvalue reference to an ostream as first argument. So C++0X would
certainly support inserting objects onto a temporary stream! :-) I
only wonder why the old "C++03" operator<< functions (having an lvalue
reference as stream argument) aren't removed from the Draft...
Vidar Hasfjord wrote:
An alternative wrapper solution that I use is based on
forwarding the << operator call:
struct S { // string builder
std::ostringstream s;
template <typename T>
inline S& operator << (const T& v) {
using ::operator <<; // Note: Ugly disambiguation.
s << v;
return *this;
}
inline operator std::string () const
{return s.str ();}
};
Usage:
string s = S () << "i = " << i;
Cool!!! It looks much better than my approach!
This works for me in practice. But you may stumble into problems
caused by less-than-perfect forwarding (e.g. ambiguities).
Can you please explain? All I can think of is getting into troubles,
because of an operator<< overload that would have a non-const reference
as last argument. E.g., operator<<(ostream&, Foo &). But I guess
offering such an operator<< would be bad practice anyway. Don't you
think?
Brendan wrote:
I actually like your workaround, although you should typedef
Wrapper<std::ostringstream> to make it cleaner. [...]
Oh! also, overload << something like this:
std::string operator<<(basic_ostream& stream, ToStr& tstr) {
return static_cast<std::ostringstream&>(stream).str();
}
I haven't tested that, and obviously it isn't type safe, but since <<
is left associative you should be able to:
string x = temp_ostringstream().get() << "i = " << i << to_str;
where to_str is the single instance of ToStr.
Thanks Brendan, that's a cool idea as well! (Especially if Vidar's
less-than-perfect forwarding get me into too much trouble...) Still
Vidar has another point: we should use dynamic_cast, while he doesn't
need to!
Here's a cleaned up version of the code I posted last night for inline
string formatting, which has been actually tested and works.
I'm glad you no longer pass ToStr by reference, in your cleaned up
version! So now you can remove your static ToStr::instance as well, and
pass a temporary ToStr():
string x = *temp_ostringstream().get() << "i = " << i << ToStr();
BTW, instead of temp_ostringstream::get(), we could also add a member
function, temp_ostringstream::operator*(), to be used as follows:
string x = *temp_ostringstream() << "i = " << i << ToStr();
Looks even better, doesn't it?
The new format is
STR_BEGIN << "asdf" << 123 << STR_END
...
#define STR_BEGIN ((Temporary<std::ostringstream>().get())
#define STR_END (ToStr::instance))
...
You might ask, how is this code type safe? Can't I write:
std::cout << "asdf" << STR_END;
No, you cannot.
Good point. The non-macro version would cause a runtime error
(std::bad_cast, if we're using dynamic_cast), instead:
std::cout << "asdf" << ToStr(); // runtime error
Your compile time error is surely preferable.
What do you think? Is this too much of a hack? Are there edge cases
I'm missing? Is there a better way?
Some people like macro hacks better than others... I guess it's a
matter of taste. It has to compete against my STRING macro, and Vidar's
string builder. I *think* that Vidar's less-than-perfect forwarding
issue can be fixed in C++0x (using std::forward), which would
*eventually* make his approach superior... Don't you think?
Kind regards,
Niels
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]