20
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());
m0nhawk
  • 22,980
  • 9
  • 45
  • 73
anon
  • 41,035
  • 53
  • 197
  • 293

7 Answers7

76
#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.

Community
  • 1
  • 1
Nicolás
  • 7,423
  • 33
  • 35
  • 8
    This is the most brilliant use (or abuse) of c++ destructors I have seen in my life. – anon Feb 03 '10 at 23:09
  • No, the macro creates a temporary, which is destroyed after the line containing the temporary executes. – Billy ONeal Feb 03 '10 at 23:13
  • I take back my destructor caveat; this does destruct immediately due to the temp being created. Nicely done. – Mike Seplowitz Feb 03 '10 at 23:14
  • You probably have to add a templated `operator<<` that forwards to the stringstream instead of the conversion operator. – sth Feb 03 '10 at 23:26
  • 2
    Check out this question for the newline issue. http://stackoverflow.com/questions/1134388/stdendl-is-of-unknown-type-when-overloading-operator – Jasper Bekkers Feb 03 '10 at 23:34
  • @Jasper: thanks for the pointer! I have updated the code and it now works with manipulators. – Nicolás Feb 03 '10 at 23:47
  • 12
    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 Feb 04 '10 at 01:18
  • @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 Feb 04 '10 at 19:37
  • 4
    I would get rid of the macro and use my_stream() directly, but other than that, this is awesome. +1 – kikito Mar 16 '10 at 17:40
  • 1
    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 Jun 10 '10 at 10:31
  • 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 safely http://stackoverflow.com/questions/1328568/custom-stream-manipulator-for-class/1329092#1329092 – iain Jun 10 '10 at 11:23
  • Here is a [refinement](https://stackoverflow.com/a/53288135/2074605) of this answer with a test case. – Parker Nov 13 '18 at 19:30
3

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
  • 61,365
  • 24
  • 124
  • 204
2

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
  • 74,869
  • 16
  • 134
  • 187
2

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.

cmorse
  • 337
  • 1
  • 7
  • 15
villintehaspam
  • 8,540
  • 6
  • 45
  • 76
2

Considering you have these lines included somewhere in your code, yes it is possible

#include <iostream>
#include <sstream> 

__LINE__ macro is defined by all standart compilers. So we can use it to generate a variable name wich is different each time you use the macro :)

Here is a new version that is seen as a one-statement instruction only: (EDITED)

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

// So you can use it like this 
int main() 
{ 
    if (4 != 2)
        my_macro << 4 << " hello "  << std::endl; 
    my_macro << 2 << " world !" << std::endl; 
} 

Developper probably won't need to use this macro twice on same line becasue of simplicity of operator <<. But in case you need this, you can switch the use of __LINE__ by __COUNTER__ (which is non standard!). Thanks to Quuxplusone for this tip

Astyan
  • 56
  • 3
  • Why give it a name? You can use an object `MyObject()` as a temporary & rely on it being destructed at the end of the statement. – dascandy Jul 06 '17 at 06:41
  • Clever! Notice that on all popular compilers you can use the non-standard `__COUNTER__` instead of the standard `__LINE__` and then it'll work even multiple times on the same line. However, in either case, this macro is not hygienic; if you say `if (log) my_macro << 4 << std::endl;` then you'll have a bad time. The top-voted answer, based on google-glog's LOG() destructor trick, is hygienic. – Quuxplusone Jul 06 '17 at 20:18
  • @Quuxplusone: You are right, What you asked is indeed something that is kind of "standard" when writing macros. So I updated the solution, – Astyan Mar 25 '18 at 09:42
  • I *think* that `for` trick suffices to make your macro hygienic, but it's pretty subtle and I'm not sure. (Ah, I see Nicolas has a less cluttered example below.) One way to de-clutter the current macro would be to factor out the repeated subexpression `Var(s, __LINE__)` into a variable: `#define my_macro my_macro_helper(Var(s, __LINE__))` and then `#define my_macro_helper(v) for...`. – Quuxplusone Mar 25 '18 at 16:42
1

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
  • 7,423
  • 33
  • 35
1

The logging setup I have is quite similar:

bool ShouldLog(const char* file, size_t line, Priority prio);

class LoggerOutput : public std::stringstream {
public:
  LoggerOutput(const char* file, size_t line, Priority prio) 
  : prio(prio) 
  {
    Prefix(file, line, prio);
  }
  void Prefix(const char* file, size_t line, Priority prio);
  ~LoggerOutput() {
    Flush();
  }
  void Flush();
private:
  Priority prio;
};

#define LOG(Prio) if (!Logging::ShouldLog(__FILE__, __LINE__, Prio)) {} else Logging::LoggerOutput(__FILE__, __LINE__, Prio)

If your logging is disabled, the ostream is never created and little overhead exists. You can configure logging on file name & line number(s) or priority levels. The ShouldLog function can change between invocations, so you could throttle or limit output. The log output uses two functions to modify itself, Prefix that adds a "file:line: (PRIO) " prefix to the line, and Flush() which both flushes it to the log output as a single command and adds a newline to it. In my implementation it always does, but you can make that conditional if one is not already there.

dascandy
  • 7,184
  • 1
  • 29
  • 50