Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy evaluation with ostream C++ operators

I am looking for a portable way to implement lazy evaluation in C++ for logging class. Let's say that I have a simple logging function like

void syslog(int priority, const char *format, ...);

then in syslog() function we can do:

if (priority < current_priority)
  return;

so we never actually call the formatting function (sprintf). On the other hand, if we use logging stream like

log << LOG_NOTICE << "test " << 123;

all the formating is always executed, which may take a lot of time. Is there any possibility to actually use all the goodies of ostream (like custom << operator for classes, type safety, elegant syntax...) in a way that the formating is executed AFTER the logging level is checked ?

like image 719
SavinG Avatar asked Feb 17 '11 23:02

SavinG


3 Answers

This looks like something that could be handled with expression templates. Beware, however, that expression templates can be decidedly non-trivial to implement.

The general idea of how they work is that the operators just build up a temporary object, and you pass that temporary object to your logging object. The logging object would look at the logging level and decide whether to carry out the actions embodied in the temporary object, or just discard it.

like image 68
Jerry Coffin Avatar answered Sep 24 '22 13:09

Jerry Coffin


What I've done in our apps is to return a boost::iostreams::null_stream in the case where the logging level filters that statement. That works reasonably well, but will still call all << operators.

If the log level is set at compile time, you could switch to an object with a null << operator.

Otherwise, it's expression templates as Jerry said.

like image 26
Macke Avatar answered Sep 22 '22 13:09

Macke


The easiest and most straight-forward way is to simply move the check outside of the formatting:

MyLogger log;  // Probably a global variable or similar.

if (log.notice())
  log << "notified!\n" << some_function("which takes forever to compute"
    " and which it is impossible to elide if the check is inside log's"
    " op<< or similar");

if (log.warn()) {
  log << "warned!\n";
  T x;
  longer_code_computing(value_for, x);  // easily separate out this logic
  log << x;
}

If you really wanted to shorten the common case, you could use a macro:

#define LOG_NOTICE(logger) if (logger.notice()) logger <<

LOG_NOTICE(log) << "foo\n";
// instead of:
if (log.notice()) log << "foo\n";

But the savings is marginal.

One possible MyLogger:

struct MyLogger {
  int priority_threshold;

  bool notice()  const { return notice_priority  < current_priority; }
  bool warn()    const { return warn_priority    < current_priority; }
  bool etc()     const { return etc_priority     < current_priority; }

  template<class T>
  MyLogger& operator<<(T const &x) {
    do_something_with(x);
    return *this;
  }
};

The problem here is mixing iostream-style operator overloading with a printf-like logging function – specifically translating manipulators and formatting flags/fields from iostreams into a format string. You could write to a stringstream and then chunk that to your syslog function, or try something fancier. The above MyLogger works easiest if it also contains an ostream reference to which it can forward, but you'll need a few more op<< overloads for iomanips (e.g. endl) if you do that.

like image 37
Fred Nurk Avatar answered Sep 24 '22 13:09

Fred Nurk