• 1
Votes
name
name Punditsdkoslkdosdkoskdo

Formatting a macro to work with single line if statements in c++

I am using the boost logging framework, which might be irrelevant for this question, but I want to have a macro in the form of LOG(sev) where sev is one of the log levels and I can standardize the format of the output.

#define LOG_LOCATION 
  boost::log::attribute_cast>(boost::log::core::get()->get_global_attributes()["Line"]).set(__LINE__); 
  boost::log::attribute_cast>(boost::log::core::get()->get_global_attributes()["File"]).set(__FILE__); 
  boost::log::attribute_cast>(boost::log::core::get()->get_global_attributes()["Function"]).set(__func__);

#define LOG(sev) LOG_LOCATION BOOST_LOG_SEV(slg, sev)

extern boost::log::sources::severity_logger slg;

This code snippet works in most cases where the log is on a single line, however, if I use an if in the format.

if(false) LOG(debug) << "Don't print this";

It always prints the message. The reason is obvious, the if applies to the first statement in the macro and the rest executed, so the statement will show up (without the line number).

I am unsure of how to format this macro to work correctly.

Replace the semicolons at the end of the three function calls in your LOG_LOCATION macro with commas. This turns three statements into one partial one, that continues after the end of the expanded macro.

#define LOG_LOCATION 
  boost::log::attribute_cast<boost::log::attributes::mutable_constant<int>>(boost::log::core::get()->get_global_attributes()["Line"]).set(__LINE__), 
  boost::log::attribute_cast<boost::log::attributes::mutable_constant<std::string>>(boost::log::core::get()->get_global_attributes()["File"]).set(__FILE__), 
  boost::log::attribute_cast<boost::log::attributes::mutable_constant<std::string>>(boost::log::core::get()->get_global_attributes()["Function"]).set(__func__),

When used as you have it above, the if line will become

if(false) a, b, c, BOOST_LOG_SEV(slg, sev) << "Don't print this";

(Replacing those three calls to set with letters for brevity.)

This would work if BOOST_LOG_SEV expands to a single expression. However, BOOST_LOG_SEV expands to a for statement:

for (::boost::log::record rec_var = (logger).open_record((BOOST_PP_SEQ_ENUM(params_seq))); !!rec_var;)
    ::boost::log::aux::make_record_pump((logger), rec_var).stream()

So a different approach is necessary.

You can define 'LOG' as a class (instead of a macro) to encapsulate all the stuff in those macros.

class LOG {
public:
    LOG(int sev): sev(sev) { LOG_LOCATION; }
    LOG &operator<<(const char *msg) {
        BOOST_LOG_SEV(slg, sev) << msg;
        return *this;
    }
}

Depending on what your needs are, you can add other overloads of operator<< to handle std::string, int, or just make it a template class.

This may not be quite as efficient as the original when sending multiple items (LOG(debug) << "Number " << x << " found.") but will work as a single statement.

  • 1
Reply Report