views:

2422

answers:

7

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?

+3  A: 

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)

coppro
I'll upvote the first paragraph, but not the whole answer.
David Norman
Thanks, that's very informative. I haven't used Boost much, it's interesting to see what's in there.
cadabra
Why can't it bind?
Mr Fooz
Because temporaries can't bind to non-const references. For instance, `string` is not valid. As such, the overload for `char *` (and many other overloads) isn't chosen.
coppro
David: hah. The original poster specifically mentioned variadic templates, which is what this egregious preprocessor abuse reproduces (though not half as well).
coppro
Don't get me wrong: You answered the hard part of the question.
David Norman
+1  A: 

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.

cadabra
std::dec tells the stream to render numbers as base 10 or decimal.
Jere.Jones
It uses one of the member formatters to then return a reference, which can be bound to a temporary. std::dec is a good parameter to pass that won't have a side effect on the output of the stream.
coppro
A: 

Why not just use a function instead of a macro?

Paul Nathan
because he wants to use the insertion operator syntax
Mr Fooz
I suppose I could manually write N template functions, one for each number of arguments and then use format(a, "b", c, d). Or use coppro's solution to generate them. But neither is pretty.
cadabra
+2  A: 

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.

David Norman
cadabra
clever tweak, cadabra
Mr Fooz
I only tried Microsoft VisualStudio 2008
David Norman
+12  A: 
e.James
Your second example is going to give you a pointer into a temporary string that has been destroyed - good luck with that.
Mark Ransom
I tried that portion out, and it doesn't compile anyway. I have removed it from my answer! Thank you for the input.
e.James
no need for a macro: put template<class T> StringMaker return *this; } inside your class definition and remove "ref"
Johannes Schaub - litb
@litb: That is much more elegant. Thank you! I have updated my answer accordingly
e.James
Thanks eJames, this is an excellent solution.
cadabra
Nice work. Note that it *is* possible to safely implicitly produce a C-style string, provided that string will only be used for the duration of the expression. See dribeas' closely related work here: http://stackoverflow.com/questions/469696/what-is-your-most-useful-c-c-snippet/470999#470999
j_random_hacker
@j_random_hacker: Thank you. I usually prefer the string class over c-style strings, but it is good to know that I can use them for those times when cold, hard reality intrudes on my wishful thinking :)
e.James
I independently created the exact same class, almost verbatim. You might want to also add template <typename T_Stream>T_Stream }So you can insert it into something else when it is done. Although I admit this adds very little to functionality, since you could already cast explicitly to a string.
Mark Borgerding
@Mark Borgerding: I tried adding that template, but it throws me a screen full of compiler errors when I try to test it out. Can you elaborate on how it should be done? You can always post an answer if you need to show some code :)
e.James
+8  A: 

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 class ostringstream methods. (Yes, we need to use .str(). Using the ostringstream object directly would wind up invoking ios::operator void*(), returning a pointer-like good/bad value and not a string object.)

  • operator<<(...) exists as both inherited ostream methods and global functions. In all cases it returns an ostream& reference.

  • The choices here for ostringstream()<<"foo" are the inherited method ostream::operator<<(void* ) and the global function operator<<(ostream&,const char* ). The inherited ostream::operator<<(void* ) wins out because we can't convert to an ostream 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:

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:

.

Mr.Ree
+1  A: 

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.

DevSolar