问题
I'm trying to write a macro that would allow me to do something like: FORMAT(a << "b" << c << d)
, and the result would be a string -- the same as creating an ostringstream, inserting a...d
, and returning .str()
. Something like:
string f(){
ostringstream o;
o << a << "b" << c << d;
return o.str()
}
Essentially, FORMAT(a << "b" << c << d) == f()
.
First, I tried:
1: #define FORMAT(items) \
((std::ostringstream&)(std::ostringstream() << items)).str()
If the very first item is a C string (const char *
), it will print the address of the string in hex, and the next items will print fine. If the very first item is an std::string
, it will fail to compile (no matching operator <<
).
This:
2: #define FORMAT(items) \
((std::ostringstream&)(std::ostringstream() << 0 << '\b' << items)).str()
gives what seems like the right output, but the 0
and \b
are present in the string of course.
The following seems to work, but compiles with warnings (taking address of temporary):
3: #define FORMAT(items) \
((std::ostringstream&)(*((std::ostream*)(&std::ostringstream())) << items)).str()
Does anyone know why 1 prints the address of the c-string and fails to compile with the std::string
? Aren't 1 and 3 essentially the same?
I suspect that C++0x variadic templates will make format(a, "b", c, d)
possible. But is there a way to solve this now?
回答1:
You've all pretty much nailed this already. But it's a little challenging to follow. So let me take a stab at summarizing what you've said...
That difficulties here are that:
We are playing with a temporary
ostringstream
object, so taking addresses is contra-indicated.Because it's a temporary, we cannot trivially convert to an
ostream
object through casting.Both the constructor [obviously] and
str()
are classostringstream
methods. (Yes, we need to use.str()
. Using theostringstream
object directly would wind up invokingios::operator void*()
, returning a pointer-like good/bad value and not a string object.)operator<<(...)
exists as both inheritedostream
methods and global functions. In all cases it returns anostream&
reference.The choices here for
ostringstream()<<"foo"
are the inherited methodostream::operator<<(void* )
and the global functionoperator<<(ostream&,const char* )
. The inheritedostream::operator<<(void* )
wins out because we can't convert to anostream
object reference to invoke the global function. [Kudos to coppro!]
So, to pull this off, we need to:
- Allocate a temporary
ostringstream
. - Convert it to an
ostream
. - Append data.
- Convert it back to an
ostringstream
. - And invoke
str()
.
Allocating: ostringstream()
.
Converting: There are several choices. Others have suggested:
ostringstream() << std::string() // Kudos to *David Norman*
ostringstream() << std::dec // Kudos to *cadabra*
Or we could use:
ostringstream() . seekp( 0, ios_base::cur )
ostringstream() . write( "", 0 )
ostringstream() . flush()
ostringstream() << flush
ostringstream() << nounitbuf
ostringstream() << unitbuf
ostringstream() << noshowpos
- Or any other standard manipulator. [
#include <iomanip>
] Reference: See "Insert data with format" 1/3 of the way down on this webpage.
We cannot use:
operator<<( ostringstream(), "" )
(ostream &) ostringstream()
Appending: Straightforward now.
Converting back: We could just use (ostringstream&)
. But a dynamic_cast
would be safer. In the unlikely event dynamic_cast
returned NULL
(it shouldn't), the following .str()
will trigger a coredump.
Invoking str()
: Guess.
Putting it all together.
#define FORMAT(ITEMS) \
( ( dynamic_cast<ostringstream &> ( \
ostringstream() . seekp( 0, ios_base::cur ) << ITEMS ) \
) . str() )
References:
- IOstream Library
- ostringstream
- ostream::operator<<()
- Type Casting Tutorial
- Wiki: Type Casting
.
回答2:
Here is what I use. It all fits into one tidy class definition in a header file.
update: major improvement to the code thanks to litb.
// makestring.h:
class MakeString
{
public:
std::stringstream stream;
operator std::string() const { return stream.str(); }
template<class T>
MakeString& operator<<(T const& VAR) { stream << VAR; return *this; }
};
Here is how it is used:
string myString = MakeString() << a << "b" << c << d;
回答3:
The problem you are having is related to the fact that operator << (ostream&, char*)
is not a member of ostream, and your temporary ostream instance cannot bind to a non-const
reference. Instead, it picks the void*
overload, which is a member of ostream, and thus doesn't have that restriction.
The best (but not easiest or most elegant, by any stretch of imagination!) would be to use the Boost Preprocessor to generate a large number of function overloads, each templated on a large number of objects (includes have been omitted and assuming using namespace std;
):
#define MAKE_OUTPUT(z, n, data) \
BOOST_PP_TUPLE_ELEM(2, 0, data) << BOOST_PP_CAT(BOOST_PP_TUPLE_ELEM(2, 1, data), n);
#define MAKE_FORMAT(z, n, data) \
template <BOOST_PP_ENUM_PARAMS_Z(z, BOOST_PP_INC(n), typename T)> \
inline string format(BOOST_PP_ENUM_BINARY_PARAMS_Z(z, BOOST_PP_INC(n), T, p)) \
{ \
ostringstream s; \
BOOST_PP_REPEAT_##z(z, n, MAKE_OUTPUT, (s, p)); \
return s.str(); \
}
It's not guaranteed to work exactly (wrote it without testing), but that's basically the idea. You then call BOOST_PP_REPEAT(N, MAKE_FORMAT, ())
to create a series of functions taking up to N parameters that will format your string as you want to (replace N with the integer of choice. Higher values may negatively affect compile times). This should suffice until you get a compiler with variadic templates. You should read the boost preprocessor documentation, it has very powerful features for things like this. (you can subsequently #undef
the macros, after calling the BOOST_PP_REPEAT
invocation to generate the functions)
回答4:
Here's an answer like cadabra's that doesn't mess with the ostream state:
#define FORMAT(items) static_cast<std::ostringstream &>((std::ostringstream() << std::string() << items)).str()
I believe the first paragraph of coppro's answer describes why things behave this way.
回答5:
Here's a working solution:
#define FORMAT(items) \
((std::ostringstream&)(std::ostringstream() << std::dec << items)).str()
I don't quite understand the behavior of the first argument.
回答6:
When I took mrree's solution (the one marked "preferred", the one beautifully explained, and the one working perfectly for G++), I ran into problems with MSVC++: All strings built with this macro ended up empty.
Hours (and lots of scratching my head and asking a "reloaded" question here) later, I found out that the seekp() call was the culprit. I am not sure what MSVC++ does differently with that, but replacing
ostringstream().seekp( 0, ios_base::cur )
with cadabra's
ostringstream() << std::dec
works for MSVC++, too.
回答7:
Why not just use a function instead of a macro?
来源:https://stackoverflow.com/questions/303562/c-format-macro-inline-ostringstream