views:

474

answers:

6
my_macro << 1 << "hello world" << blah->getValue() << std::endl;

should expand into:

std::ostringstream oss;
oss << 1 << "hello world" << blah->getValue() << std::endl;
ThreadSafeLogging(oss.str());

Thanks!

EDIT: the accepted answer is awesome. Can we upvote 8 more times and win this responder a badge?

(The answer only needs 6 more upvotes).

4 more votes to go from 21 to 25.

3 more. :-)

Victory. :-)

+2  A: 

No. The problem is that without using function syntax, a macro is limited to only being replaced where it is.

But if you were willing to use function syntax, you can then replace stuff both before and after the args.

my_macro(1 << "hello world" << blah->getValue() << std::endl);

You could by defining MyMacro as:

#define my_macro(args) std::ostreamstring oss; \
                       oss << args; \
                       ThreadSafeLogging(oss.str());
R Samuel Klatchko
Given Nicolás' answer, it appears that "No." is incorrect.
Graeme Perrow
+45  A: 
#define my_macro my_stream()
class my_stream: public std::ostringstream  {
public:
    my_stream() {}
    ~my_stream() {
        ThreadSafeLogging(this->str());
    }
};
int main() {
    my_macro << 1 << "hello world" << std::endl;
}

A temporary of type my_stream is created, which is a subclass of ostringstream. All operations to that temporary work as they would on an ostringstream.

When the statement ends (ie. right after the semicolon on the whole printing operation in main()), the temporary object goes out of scope and is destroyed. The my_stream destructor calls ThreadSafeLogging with the data "collected" previously.

Tested (g++).

Thanks/credits to dingo for pointing out how to simplify the whole thing, so I don't need the overloaded operator<<. Too bad upvotes can't be shared.

Nicolás
This is the most brilliant use (or abuse) of c++ destructors I have seen in my life.
anon
No, the macro creates a temporary, which is destroyed after the line containing the temporary executes.
Billy ONeal
I take back my destructor caveat; this does destruct immediately due to the temp being created. Nicely done.
MikeSep
this is genius. that said, I'm not sure it's a good idea.
rmeador
You probably have to add a templated `operator<<` that forwards to the stringstream instead of the conversion operator.
sth
Check out this question for the newline issue. http://stackoverflow.com/questions/1134388/stdendl-is-of-unknown-type-when-overloading-operator
Jasper Bekkers
@Jasper: thanks for the pointer! I have updated the code and it now works with manipulators.
Nicolás
Wouldn't it be simpler to just use std::ostringstream as the base class for my_stream? It would seem then that the destructor is the only method needed.
Dingo
@Dingo: YES! The trick I posted is something I used in a quite messier case, but in *this* case, subclassing is indeed enough. Good thinking.
Nicolás
I would get rid of the macro and use my_stream() directly, but other than that, this is awesome. +1
egarcia
How can you have so many votes for a trick that really is bad C++ and that doesn't work and can lead to problems. ( try `my_macro << std::string( "surprise " )` ) Streams in C++ are not supposed to be used as temporaries. The cost of create your << and >> operator is not that big
Nikko
It is safer not to use inheritance for this since my_macro will not work with with global function operator <<. See my answer to a previous post on how to do this safelyhttp://stackoverflow.com/questions/1328568/custom-stream-manipulator-for-class/1329092#1329092
iain
+3  A: 

Couldn't you just derive from ostream and provide your own thread safe implementation? Then you could just do

myCOutObject << 1 << "hello world" << blah->getValue() << std::endl;

And get the exact same functionality without macros and using C++ properly?

Goz
A: 

Take a look at google-glog, they do this using a temporary object instanciated with a

LOG(INFO) << "log whatever" << 1;

and they also have other interesting macros such as LOG_IF et al.

villintehaspam
A: 

Here's another nasty trick I saw somewhere else. It has a significant disadvantage compared to my other answer: you can't use it twice in the same scope because it declares a variable. However, it may still be interesting for other cases where you want to have somemacro foo run something after foo.

#define my_macro \
    std::ostringstream oss; \
    for (int x=0; x<2; ++x) \
        if (x==1) ThreadSafeLogging(oss.str()); \
        else oss

int main() {
    my_macro << 1 << "hello world" << std::endl;
}
Nicolás
A: 

Of course you can use it more than once :) ! __LINE__ macro is defined by all standart compilers. So we can use it to generate ostrinstream variable name :)

#define Var_(Name, Index) Name##Index
#define Var(Name, Index) Var_(Name, Index)
#define my_macro \
    std::ostringstream Var(oss, __LINE__);          \
for (int x=0; x<2; ++x)  \
    if (x==1) std::cout << Var(oss, __LINE__).str();    \
    else Var(oss, __LINE__)

Ok ok not two times on the same line ;p but .. who would do that ?!!

int main() { 
    my_macro << 4 << " hello "  << std::endl; 
    my_macro << 2 << " world !" << std::endl; 
} 
Astyan