Preprocessor, expand macro to nothing when undefined - c++

The preprocessor has always been black magic to me but I think I finally need to use it.
I have implemented a logger class which I want to conditionally (compile flag) expand to nothing if the flag is not set so that I don't get all my prints in production.
This would have a usecase like so
FO_LOG << name() << "Hello World" << std::endl;
I thought I could define it like this
#ifdef TRACE
#define FO_LOG {return Faceoff::trace::log();}
#else
#define FO_LOG \
if(false){\
return Faceoff::trace::log();\
}
#endif
But this won't compile with the following errors
no viable conversion from returned value of type 'Faceoff::trace' to function return type 'int'
FO_LOG << name() << "omitted" << std::endl;
^~~~~~
/omitted/include/globals.h:69:16: note: expanded from macro 'FO_LOG'
return Faceoff::trace::log();\
^~~~~~~~~~~~~~~~~~~~~
Now the error is clear, but I don't know how to express my intention in preprocessor syntax...

Here you go:
class devnull : public std::ostream {
class devnullbuff : public std::streambuf {
public:
int overflow( int c ) { return c; }
} m_nb;
public:
devnull() : std::ostream( &m_nb ) {}
};
#ifdef TRACE
#define FO_LOG Faceoff::trace::log()
#else
#define FO_LOG devnull()
#endif
Assuming Faceoff::trace::log() returns some sort of ostream, then you can do what you want:
FO_LOG << name() << "Hello World" << std::endl;
This is not quite efficient because it creates a new devnull object every time. You can just create it once in your program and forward declare to it to avoid this. It's not as elegant though.

Related

Overloading C Preprocessor Macros - Discimination Based on Call Syntax

I'm currently working on a cpp logger which aims at displaying the __FILE__ and the __LINE__ before each printed message. In my case, we are mostly using 2 methods for printing out: printf-style and std::cout-style. For the moment I have a macros for each style:
#define HATFormatFatal(...) HATLogger::logFormat(HATLogger::LogLevel::FATAL, __FILE__, __LINE__, __VA_ARGS__)
#define HATFormatError(...) HATLogger::logFormat(HATLogger::LogLevel::ERROR, __FILE__, __LINE__, __VA_ARGS__)
etc... and:
#define HATStreamFatal HATLogger::logStream(HATLogger::LogLevel::FATAL, __FILE__, __LINE__)
#define HATStreamError HATLogger::logStream(HATLogger::LogLevel::ERROR, __FILE__, __LINE__)
These macros can be called in the following:
HATFormatError("This is an %s message", "ERROR");
HATStreamError << "This is an " << "ERROR" << " message" << std::endl;
I would like to call them with the same name: HATLogError. The right macro would be determine at compilation while looking for parenthesis. So far I've seen some examples showing how it is possible to discriminate the macros by the number of arguments, but nothing that could handle a "non-parenthesis" case.
Does anyone have any idea on how this could be achieved ?
The simplest approach would be not overloading the macro at all, but instead having the macro return an object that has both operator<< and operator() overloaded. Something like this:
class error_logger {
public:
error_logger(
HATLogger::LogLevel level,
char const * file,
char const * line
) : level{level}, file{file}, line{line} { }
template <typename... T>
void operator()(T && ... args) {
HATLogger::logFormat(level, file, line, std::forward<T>(args)...);
}
template <typename T>
HATLogger::logStream operator<<(T && arg) {
HATLogger::logStream stream{level, file, line};
stream << std::forward<T>(arg);
return stream;
}
private:
HATLogger::LogLevel level;
char const * file;
char const * line;
};
(This example assumes HATLogger::logStream can be moved. Adjustments to this example implementation may need to be made based on the details of your code, but the basic approach is what I'm demonstrating here.)
Now you could do:
#define HATFormatFatal (error_logger{HATLogger::LogLevel::FATAL, __FILE__, __LINE__})
And then both HATFormatFatal << ... and HATFormatFatal(...) can be used.

LOG4CXX_INFO clean wrapper that is not a #define

I am trying to wrap a #define macro in C++ into an inline or template function. In LOG4CXX it has a define such as:
#define LOG4CXX_INFO(logger, message) { \
if (logger->isInfoEnabled()) {\
::log4cxx::helpers::MessageBuffer oss_; \
logger->forcedLog(::log4cxx::Level::getInfo(), oss_.str(oss_ << message), LOG4CXX_LOCATION); }}
I am needing to add other context to the logs and have wrapped LOG4CXX_INFO in another define and have this implementation working. I am wanting to move away from using a #define to wrap this and do it in a more C++ way. This issue I am running into is that the message param can be of various types of objects concated together using + or << operators and this is currently handled in the MessageBuffer class with operator overrides.
If I try to template it such as:
template<typename T>
void info(LoggerPtr logger, const T& message) {
...do stuff ...
LOG4CXX_INFO(logger, message);
}
The compiler will complain about not finding << operators for various types of objects.
What needs to be done to make this work?
Compiled with the following on ubuntu
gcc main.cpp -lstdc++ -llog4cxx
#include <log4cxx/logger.h>
using namespace std;
#define info(logger, message) LOG4CXX_INFO(logger, message)
template<typename T> void tinfo(log4cxx::LoggerPtr logger, const T& message) {
LOG4CXX_INFO(logger, message);
}
int main()
{
log4cxx::LoggerPtr logger;
LOG4CXX_INFO(logger, "test" << "test2");
info(logger,"another test");
info(logger,"another test" << "another test2");
tinfo(logger,"test template");
//tinfo(logger,"test template" << "test template 2"); -> this line does not compile
return 0;
}
Compiler error:
main.cpp: In function ‘int main()’:
main.cpp:21:31: error: invalid operands of types ‘const char [14]’ and ‘const char [16]’ to binary ‘operator<<’
tinfo(logger,"test template" << "test template 2");
MessageBuffer code can be found here: https://github.com/apache/logging-log4cxx/blob/master/src/main/include/log4cxx/helpers/messagebuffer.h and here https://github.com/apache/logging-log4cxx/blob/master/src/main/cpp/messagebuffer.cpp
tinfo(logger,"test template" << "test template 2");
is wrong. It first evaluates "test template" << "test template 2" and then passes the result to the function. But that makes no sense, since the left-hand side of << should be a stream object.
This works with the macro, because macros are pure text substitution, so the substitution
LOG4CXX_INFO(logger, "test" << "test2");
yields
oss_ << "test" << "test2"
which is evaluated left-to-right.
If you add parentheses around the macro argument
LOG4CXX_INFO(logger, ("test" << "test2"));
you reproduce the function behavior faithfully and you will get the same error because you would be evaluating
oss_ << ("test" << "test2")
If you want to output multiple things in one function call I suggest using a fold expression (available since C++17):
template<typename... Ts>
void tinfo(log4cxx::LoggerPtr logger, const Ts&... messages) {
if (logger->isInfoEnabled()) {
::log4cxx::helpers::MessageBuffer oss_;
logger->forcedLog(::log4cxx::Level::getInfo(), oss_.str((oss_ << ... << messages)), LOG4CXX_LOCATION);
}
}
tinfo(logger, "test template", "test template 2");
If you want to keep the original syntax and/or refer to LOG4CXX_INFO instead of reimplementing the body of that macro, then I don't think it is possible without macros.

Any portable tricks to obtain namespace name in C++?

I have some well-formed code looks like this:
NAMESPACE_BEGIN(Foo)
inline void test() {
string s = xxx;
}
NAMESPACE_END(Foo)
So, is there any portable tricks by using the NAMESPACE_BEGIN() macro to obtain namespace name "Foo" in test()?
I'm thinking of something like this, but it would surely cause symbol redefinition:
#define NAMESPACE_BEGIN(x) \
namespace x { \
inline const char *_curNamespace() { \
return #x; \
}
#define NAMESPACE_END(x) \
}
There's also a workaround looks like this, but that's not very convenient
#define NAMESPACE_NAME Foo
// using a header file so that we can use #ifdef guard
#include "MyNamespaceBegin.h"
...
#include "MyNamespaceEnd.h"
EDIT:
Why I need this:
I'm using much of macro to generate codes to achieve some
dynamic reflection logic (yes, not static template reflection),
it's all right within class scope by using static member function,
but does not work for namespaces
Why not to manually declare the name getter once:
What I want is something like this:
// the global default version
const char *_curNamespace() {return "";}
namespace X {
// the local namespace version
const char *_curNamespace() {return "X";}
// some verbose reflection register code
...
registerSomething(_curNamespace());
...
}
Of course, all of the verbose register code should be generated by macro
And, app level user should not care about the _curNamespace(),
so, I want to simplify the user's usage,
by using a custom NAMESPACE_BEGIN(xxx) macro at any case
If you are still curious about what I'm doing,
check this: https://github.com/ZFFramework/ZFFramework
I'm using lots of tricks to achieve fully dynamic reflection in pure C++,
to achieve some of my fancy thoughts,
for now, this project is just for fun,
I have no idea whether it has practicability
EDIT2:
For now, I think the best workaround should be like this:
#define NAMESPACE_BEGIN(ns) \
namespace ns { \
extern const char *__curNS();
#define NAMESPACE_END(ns) \
}
#define NAMESPACE_REG(ns) \
const char *__curNS() {return #ns;}
app level users still only need to care about NAMESPACE_BEGIN
NAMESPACE_REG must be declared exactly once, in source file
if not, undefined symbol would happen
if more than once, duplicated symbol would happen
although it's annoying and sometimes you need additional source file
to hold the NAMESPACE_REG,
the strict rule should prevent user from forgetting the ugly workaround
You are making much fuss over something that is trivial to implement.
First of all, use of NAMESPACE_BEGIN and NAMESPACE_END seems unnecessary to me. I don't see how that is more readable or useful than
namespace Foo
{
}
If getting the name of the namespace is important/useful, add a trivial function.
namespace Foo
{
inline std::string get_name() { return "Foo"; }
}
Small sized real world applications need thousands of lines of code. Large sized real world applications need millions of lines of code. From that perspective, implementing a one line inline function is a very minor task.
This solution employs a bit of preprocessor magic and has these features:
Namespace is mentioned only once
Access to a macro containing the unquoted name
Access to a macro containing the quoted name
Support for repeating the same namespace
Support for different namespaces
Misuse of the BEGIN/END macros is detected
Cleanup, i.e. no extra macros defined outside the BEGIN/END block
It does not support nested namespaces.
Example of usage:
#include "framework.hpp"
#define NAMESPACE_NAME Foo
#include NAMESPACE_BEGIN
// Here you have access to NAMESPACE_NAME (unquoted, i.e. Foo)
// and also to NAMESPACE_NAME_STRING (quoted, i.e. "Foo")
#include NAMESPACE_END
// NAMESPACE_NAME and NAMESPACE_NAME_STRING do not exist
// outside the block, so they cannot be misused
// Different namespaces in the same TU are supported
#define NAMESPACE_NAME Bar
#include NAMESPACE_BEGIN
inline std::string f()
{
return NAMESPACE_NAME_STRING;
}
#include NAMESPACE_END
// Repeating the same namespace is also supported
#define NAMESPACE_NAME Foo
#include NAMESPACE_BEGIN
inline std::string f()
{
return NAMESPACE_NAME_STRING;
}
#include NAMESPACE_END
The implementation follows:
framework.hpp
#pragma once
#define NAMESPACE_BEGIN "framework_namespace_begin.hpp"
#define NAMESPACE_END "framework_namespace_end.hpp"
framework_namespace_begin.hpp
#ifndef NAMESPACE_NAME
#error "NAMESPACE_NAME not defined"
#endif
#define NAMESPACE_IN_NAMESPACE 1
#define NAMESPACE_NAME_DO_STR(X) #X
#define NAMESPACE_NAME_STR(X) NAMESPACE_NAME_DO_STR(X)
#define NAMESPACE_NAME_STRING NAMESPACE_NAME_STR(NAMESPACE_NAME)
namespace NAMESPACE_NAME {
framework_namespace_end.hpp
#ifndef NAMESPACE_IN_NAMESPACE
#error "NAMESPACE_IN_NAMESPACE not defined"
#endif
}
#undef NAMESPACE_NAME
#undef NAMESPACE_NAME_STRING
#undef NAMESPACE_IN_NAMESPACE
You know what? I think I might just have a viable solution for this. It's actually very simple, and it's very close to the OP's original suggestion (which really only had the problem of a potential duplicate definition if you wanted to open the namespace twice in the same translation unit). You just have to think a bit laterally and not be too precious about seeing your namespaces being bracketed by macros instead of curly braces.
So let me just lay it out here, because there's really nothing to it, and then I'll explain why I personally happen to like it.
Code:
Macros:
#define DECLARE_NAMESPACE(ns) \
namespace ns {\
static constexpr const char *_curNamespace = #ns; \
}
#define BEGIN_NAMESPACE(ns) \
namespace ns { \
static_assert (ns::_curNamespace, "BEGIN_NAMESPACE: namespace has not been declared");
#define END_NAMESPACE }
Sample code:
#include <iostream>
DECLARE_NAMESPACE (Foo)
BEGIN_NAMESPACE (Foo)
void print_namespace_name () { std::cout << _curNamespace << "\n"; }
END_NAMESPACE
BEGIN_NAMESPACE (Foo)
void another_print_namespace_name () { std::cout << _curNamespace << "\n"; }
END_NAMESPACE
DECLARE_NAMESPACE (Bar)
BEGIN_NAMESPACE (Bar)
void print_namespace_name () { std::cout << _curNamespace << "\n"; }
DECLARE_NAMESPACE (BarBar)
BEGIN_NAMESPACE (BarBar)
void print_namespace_name () { std::cout << _curNamespace << "\n"; }
END_NAMESPACE
END_NAMESPACE
int main ()
{
Foo::print_namespace_name ();
Foo::another_print_namespace_name ();
Bar::print_namespace_name ();
Bar::BarBar::print_namespace_name ();
}
Output:
Foo
Foo
Bar
BarBar
Now this is obviously very straightforward to implement and also easy to use and has no obvious limitations. In particular, it can handle nested namespaces (as shown in the code above) and opening a namespace twice in the same compilation unit also works (again, this is shown in the code snippet).
But, but, but, don't we still have to type in the name of the namespace twice, and wasn't that the very thing we were trying to avoid to eliminate typos?
Well, sure, we have to type the name in twice, but so what, live with it. Point is, with this particular set of macros, the compiler will now catch any typos for us. Let's prove that by deliberately putting one in. So this:
DECLARE_NAMESPACE Whoops
BEGIN_NAMESPACE whoops
END_NAMESPACE
Generates this (I couldn't find a better way of formulating the static_assert, sorry):
prog.cc:12:24: error: '_curNamespace' is not a member of 'whoops'
static_assert (ns::_curNamespace, "BEGIN_NAMESPACE: namespace has not been declared");
^~~~~~~~~~~~~
prog.cc:27:5: note: in expansion of macro 'BEGIN_NAMESPACE'
BEGIN_NAMESPACE (whoops)
^~~~~~~~~~~~~~~
And more importantly this (and this is why we need the BEGIN_NAMESPACE macro):
DECLARE_NAMESPACE (Bar)
BEGIN_NAMESPACE (Bar)
DECLARE_NAMESPACE (BarWhoops)
BEGIN_NAMESPACE (Barwhoops)
END_NAMESPACE
END_NAMESPACE
Generates this:
prog.cc:12:24: error: '_curNamespace' is not a member of 'Bar::Barwhoops'
static_assert (ns::_curNamespace, "BEGIN_NAMESPACE: namespace has not been declared");
^~~~~~~~~~~~~
prog.cc:42:5: note: in expansion of macro 'BEGIN_NAMESPACE'
BEGIN_NAMESPACE (Barwhoops)
^~~~~~~~~~~~~~~
Which is just dandy.
So, you know, what's not to like?
Live demo - uncomment line 3 to see those compiler errors.
you can use a variable and change its value with 'NAMESPACE_BEGIN' and 'NAMESPACE_END'
the variable __name represent the current full namespace position
like "abc::def::detail"
std::string __name = "";
std::string & __append(std::string & str, const char * ptr) {
if (!str.empty()) {
str.append("::");
}
str.append(ptr);
return str;
}
std::string & __substr(std::string & str, const char * ptr) {
if (str.length() == strlen(ptr)) {
str.clear();
} else {
str = str.substr(0, str.length() - strlen(ptr) - 2);
}
return str;
}
#define NAMESPACE_NAME __name
#define CONCATENATE_DIRECT(s1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_DIRECT(s1, s2)
#ifdef _MSC_VER
# define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __COUNTER__)
#else
# define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__)
#endif
#define APPEND_NAME(x) std::string ANONYMOUS_VARIABLE(__start_name) = __append(__name, #x)
#define SUBSTR_NAME(x) std::string ANONYMOUS_VARIABLE(__end_name) = __substr(__name, #x)
#define NAMESPACE_BEGIN(x) \
namespace x { \
APPEND_NAME(x);
#define NAMESPACE_END(x) \
SUBSTR_NAME(x); \
}
then you can use the NAMESPACE_NAME macro for the full name or you can extract the last name from it
Here's a way. The core idea came from the line of thought:
Q: How can I define multiple things with the same name accessible from the same scope?
A: Make them all functions with different parameter types. And if all of them have identical bodies, it doesn't matter which one gets called.
Q: How can I generate an unlimited set of different parameter types?
A: A class template.
Q: How can I make sure a call to that set of overloaded functions will never be ambiguous?
A: Make sure the binary relation "is implicitly convertible from" is a complete ordering on the parameter types, and use a unique minimal element for the argument type.
#include <type_traits>
#include <functional>
struct NamespaceHandleObj {
template <const NamespaceHandleObj* Handle1, const NamespaceHandleObj* Handle2>
struct less : public std::bool_constant<std::less<>{}(Handle1, Handle2)> {};
};
template <>
struct NamespaceHandleObj::less<nullptr, nullptr> : public std::false_type {};
template <const NamespaceHandleObj* Handle1>
struct NamespaceHandleObj::less<Handle1, nullptr> : public std::false_type {};
template <const NamespaceHandleObj* Handle2>
struct NamespaceHandleObj::less<nullptr, Handle2> : public std::true_type {};
template <const NamespaceHandleObj* Handle>
struct NamespaceParamType
{
constexpr NamespaceParamType() noexcept = default;
template <const NamespaceHandleObj* Other,
typename = std::enable_if_t<NamespaceHandleObj::less<Other, Handle>::value>>
constexpr NamespaceParamType(NamespaceParamType<Other>) noexcept {}
};
#define NAMESPACE_UTILS_TOSTR1(x) #x
#define NAMESPACE_UTILS_TOSTR(x) NAMESPACE_UTILS_TOSTR1(x)
#define BEGIN_NAMESPACE(ns) \
namespace ns { \
namespace { \
constexpr NamespaceHandleObj namespace_handle_{}; \
constexpr const char* current_ns_(
NamespaceParamType<&namespace_handle_>) noexcept \
{ return NAMESPACE_UTILS_TOSTR(ns); } \
}
#define END_NAMESPACE }
#define CURRENT_NAMESPACE (current_ns_(NamespaceParamType<nullptr>{}))
The code above is C++17, but it wouldn't be hard to port it to previous versions, even all the way to C++03.

conditional derivative usage of macro in cpp

I have a question. In one of my projects i am using the FLAG PRINT to enable/disable the debug printfs. currently I am using something like this.
#ifdef PRINT
printf("DEBUG");
#endif
It is a pain to put this #ifdef before every printf. So I was thinking to 've a #define for the #ifdef, something like
#define DEBUG_PRINT
#define PRINT (#ifdef DEBUG_PRINT)
#define ENDPRINT #endif
so that I can use like
PRINT
printf("DEBUG");
ENDPRINT
but it is giving a compiler error. Can you tell me someway to simplify.
Thanks,
A standard way is
#ifdef DEBUG_PRINT
# define is_debug() (1)
#else
# define is_debug() (0)
#endif
#define pr_dbg(fmt, ...) do { \
if (is_debug()) \
printf(fmt, __VA_ARGS__); \
} while (0)
When using gcc, you can/should write
printf(fmt, ## __VA_ARGS__);
to deal with empty args.
In your code you can write then
pr_dbg("foo=%u\n", foo);
Optimizer will throw away the expression when DEBUG_PRINT is not defined but your debug statements will be still checked for syntax errors. This avoids silent breakage when e.g. undefined variables are used within an #ifdef clause.
How about a header with
#ifdef DEBUG
#define ON_DEBUG(X,...) do { X, __VA_ARGS__; } while( false )
#else
#define ON_DEBUG(X,...) do {} while( false )
#endif
and in your code, you simply use
ON_DEBUG( printf("Hallo, %s", "Welt") );
(the do-while forces you to add the final semicolon and protects the statement's in cases if (nested) if-statements, see Aaron McDaid's comment)
I would actually do it a different way entirely. First define the flag that turns printing on or off:
// uncomment to turn off debug printing
#define DEBUG_PRINT 1
Then conditionally define your printer macro depending of the definition state of DEBUG_PRINT:
#ifdef DEBUG_PRINT
#define PRINT (X) (printf(x))
#else
#define PRINT (x)
#endif
Which can be used simply as:
PRINT("foo");
But in reality, I wouldn't do any of this stuff at all. Instead, I'd have the turn-on/turn-off flag as above, and then build a class that does the printing:
// comment out to not do debug printing
//#define DEBUG_PRINTING 1
#ifdef DEBUG_PRINTING
class Printer
{
public:
Printer() {}
~Printer()
{
std::cout << mOutput.str() << "\n";
}
template <typename TYPE> Printer& operator<< (const TYPE& val)
{
mOutput << val;
return * this;
}
template <size_t N> Printer& operator<< (const char(&ary)[N])
{
mOutput << ary;
return * this;
}
private:
std::stringstream mOutput;
};
#else
class Printer
{
public:
Printer() {};
template <typename TYPE> Printer& operator<< (const TYPE& val)
{
return * this;
}
template <size_t N> Printer& operator<< (const char(&ary)[N])
{
return * this;
}
};
#endif
int main()
{
Printer() << "My output here. " << 42;
}
In optimized builds where the flag is not defined, most (if not all) of the code will be optimized away.

Can #define preprocessor directive contain if and else?

I was trying the logger code from this link, but it gives me error. How to implement a good debug/logging feature in a project
#ifndef _LOGGER_HPP_
#define _LOGGER_HPP_
#include <iostream>
#include <sstream>
/* consider adding boost thread id since we'll want to know whose writting and
* won't want to repeat it for every single call */
/* consider adding policy class to allow users to redirect logging to specific
* files via the command line
*/
enum loglevel_e
{logERROR, logWARNING, logINFO, logDEBUG, logDEBUG1, logDEBUG2, logDEBUG3, logDEBUG4};
class logIt
{
public:
logIt(loglevel_e _loglevel = logERROR) {
_buffer << _loglevel << " :"
<< std::string(
_loglevel > logDEBUG
? (_loglevel - logDEBUG) * 4
: 1
, ' ');
}
template <typename T>
logIt & operator<<(T const & value)
{
_buffer << value;
return *this;
}
~logIt()
{
_buffer << std::endl;
// This is atomic according to the POSIX standard
// http://www.gnu.org/s/libc/manual/html_node/Streams-and-Threads.html
std::cerr << _buffer.str();
}
private:
std::ostringstream _buffer;
};
extern loglevel_e loglevel;
#define log(level) \
if (level > loglevel) ; \
else logIt(level)
#endif
More precisely, this #define is giving errors:
#define log(level) \
if (level > loglevel) ; \
else logIt(level)
The errors are Syntax error: if and Syntax error: else
But later, I noticed that if I move #include "logger.hpp" from main.h to main.cpp, the problem disappeared. Though 'main.h' is included many times in different places, it does contain '#pragma once'.
Any idea?
If loglevel is known at compile time you can do the following:
template <bool>
struct LogSystem
{
template <class T>
LogSystem& operator << (const T &)
{
//ignore the input
return (*this);
}
};
template <>
struct LogSystem <true>
{
template <class T>
LogSystem& operator << (const T & v)
{
cout << v;
return (*this);
}
};
template <bool B>
LogSystem<B> getLog()
{
return LogSystem<B>();
}
#define log(level) getLog< (level <= loglevel) >()
if loglevel is not known at compile time:
class iLogSystem
{
public:
virtual iLogSystem& operator << (const int &)
{
//empty
return (*this);
}
virtual iLogSystem& operator << (const custom_type &);
{
return (*this);
}
//make functions for logging all the types you want
};
class LogSystem : public iLogSystem
{
public:
virtual iLogSystem& operator << (const int & v)
{
cout << v;
return (*this);
}
virtual iLogSystem& operator << (const custom_type & q);
{
cout << q.toString();
return (*this);
}
//make functions for logging all the types you want
};
iLogSystem& getLog(const bool l)
{
static LogSystem actual_log;
static iLogSystem empty_log;
if(l)
return &actual_log;
return &empty_log;
}
#define log(level) getLog( level <= loglevel )
Any time you want to define a macro that expands to a statement, if the definition contains any compound statements (including if/else), you should wrap the definition in do ... while (0). The enclosed code will still execute exactly once, and it can be used in any context that requires a statement.
That's the only way I know of to avoid syntax errors when the macro is used within an if/else statement, due to the use of semicolons.
So rather that this:
#define log(level) \
if ((level) > loglevel) ; \
else logIt(level)
you can use this:
#define log(level) \
do { \
if ((level) > loglevel) ; \
else logIt(level) \
} while (0)
I've added parentheses around references to the macro's level parameter, to avoid any possible operator precedence problems. Note also the lack of a semicolon at the end; the semicolon will be supplied by the caller.
On the other hand, an if/else can often be replaced by the conditional (ternary) ?: operator:
#define log(level) \
((level) > loglevel ? 0 : logIt(level))
which allows log(level) to be used anywhere an expression can be used; that includes statement context if you add a semicolon. You might want to replace 0 by something of the type returned by logIt; if logIt is a void function, you might want:
#define log(level) \
((level) > loglevel ? (void)0 : logIt(level))
This all assumes that a macro is the right tool for the job. It's likely that a template (as suggested by this answer) or an inline function will do the job better and with less potential for confusion.
Can #define preprocessor directive contain if and else?
Yes.
Regarding your problem: preprocessor is dumb as a rock and performs only simple text subsitution. It is not a function, it is not a language construct, it is simple, dumb text subsitution. As a a result, this:
#define log(level) \
if (level > loglevel) ; \
else logIt(level)
...
log(logINFO) << "foo " << "bar " << "baz";
Turns into this:
if (logINFO > loglevel); // << here's your problem.
else
logIt(logInfo)
<< "foo " << "bar " << "baz";
Your problem is;. Here, semicolon indicates end of c++ if statement, so when compiler encounters else afterwards, it doesn't know what to do with it.
I noticed that if I move #include "logger.hpp" from main.h to main.cpp, the problem disappeared
C++ has "logarithm" function. Which is called log. If your other files use logarithm function, things will get very interesting, because it'll be replaced by your if/else logging code everywhere.
For example, if there's inlined logarithm code somewhere in a header, it'll turn into nonsense if you include your logger header first. For example log(6.0) + 1 will turn into log (if (6.0 > logLevel); else logIt(6.0)) + 1, which is not a valid C++ statement.