C-style macros with arbitrary number of parameters?

Started by
13 comments, last by Replicon 18 years, 10 months ago
So, I've got some log function, call it "log()" or something, which looks like: void Logger::log(LogLevel lev, const char *format, ...); I'm trying to wrap a call to this into a macro... something like this: #define LOG_ERROR(err) Logger::log(Logger::LOG_ERROR, err) But I get a (reasonable) error that complains about that, since calling say LOG_ERROR("hardy %s har", "har"); will have more arguments, and the preprocessor doesn't like that... Is there a way to tell the preprocessor to just take WHATEVER 'err' is gonna be, and treat it as just some text, and not try to parse all the commas? The reason I want a macro is so I can build the LOG_DEBUG macro such that it's defined as 'empty' (i.e. nothing) when '_DEBUG' is not defined, so that debug log entries I'll have scattered all over won't be compiled into my release build. Does anyone know how to do this (or do it differently?) thanks!
Advertisement
There isn't a way to do that, as far as I know (well, not one that would work in this case). Why are you using a macro and *printf-style, anyway? Just use an inline function and C++ streams:
// ...enum { ERROR_LOGGER, /* ... */ };template<int type>class Logger {};template<>class Logger<ERROR_LOGGER> : public std::ostream {public:   // All the overloaded << operator functions go here.};// ... other specializations// ... in some function ...Logger<ERROR_LOGGER> errorLogger;errorLogger << "<error>";
Of course, you don't need to use template specialization, that's just an example.
It's not really possible. Some compilers have extensions that allow varargs-like macros, e.g. for GCC:
#define LOG(fmt, ...) fprintf(logstream, fmt, __VA_ARGS__)
...but it's not at all portable. You can cheat, and do things like:
#define LOG Logger::log
Which would cause
LOG(fmt, "some string", 10)
to end up being
Logger::log(fmt, "some string", 10)
after the preprocessor has had its way with the source. I don't recommend this solution, however.
Actually, I use the method kinetik_mjg suggests you don't use...

// two defines one of which is formatted// notice the formatted one takes no arguments#define ALOG(txt) ALog::GetDefaultLog()->WriteLine(AL_EL_LOG, LOGSOURCE, txt)#define ALOGF ALog::GetDefaultLog()->WriteLine// WriteLine is this function:void WriteLine(ALogErrorLevel el, const char* source, const char* text, ...);


This works pretty well, I think [smile]. Simple and effective. I recomend you use that method - much better than compiler-specific methods.
the C++ preprocessor is a simple machine and does not have a feature like that.
The parse break on ',' and consider what follows as another argument. You can use a trick like this in order to achieve your goal:
struct Logger{   typedef enum   {      log_error,      log_system,      log_warning   } LogType;   typedef struct   {      LogType Type;      std::string Message;   } LogEntry;   static std::list<LogEntry> LogTable;   static std::stringstream Out;}#DEFINE LOG_ERROR(Log) Logger::Out.clear();/Logger::Out.str("");/Logger::Out Log:/LogTable.push_back(Logger::log_error, Out.str());int main(){   int flarp;   LOG_ERROR(<< "Hi" << " how are you?" << flarp)}

or using boost::format
[ILTUOMONDOFUTURO]
Quote:Original post by kinetik_mjg
It's not really possible. Some compilers have extensions that allow varargs-like macros, e.g. for GCC: ...but it's not at all portable.


Variadic Macros are part of the C99 standard so they are portable on C99 compliant C compilers, for the time being they are only supported as compiler vendor language extensions in C++.
I recently had the same problem (thread) and ended up with quite a nice solution:
// usage: mkstr(something << "something_else" << 123)#define mkstr(args) (static_cast<std::ostringstream&>( const_cast<std::ostream &>(static_cast< const std::ostream & >( std::ostringstream() )) << args )).str()

Then you can define a debug macro like this, for example:
#define DEBUG_LOG(args) std::cerr << mkstr(args) << std::endl;

This approach also has the advantage of not requiring variadic macros.
((std::ostringstream()) << args ).str()
you create a new temporary ostringstream everytime? I have to benchmark in order to see if your solution is quicker than mine. I think that it's better to take a central ostringstream and resetting it everytime new data comes. If I remember well in Carmack code there is something like the char* va(char*, ...) function that returns newly formatted string from an array of 3 buffer. I use a C++ style same solution.
[ILTUOMONDOFUTURO]
Good point bjogio. However, that would not work in a threaded program; you'd have to make a mechanism to get thread-specific objects.
OTOH how fast does error reporting really have to be?
Quote:Original post by 255
I recently had the same problem (thread) and ended up with quite a nice solution:

#define mkstr(args) (static_cast<std::ostringstream&>( const_cast<std::ostream &>(static_cast< const std::ostream & >( std::ostringstream() )) << args )).str()


This is the first time i've come across that thread and you know all of those casts are unnecessary and pretty wrong, do you understand whats going on with that code? do you know why Sharlin's solution doesn't work, i can tell you if you want, in any case the correct code is just:

#define mkstr(args) static_cast<std::ostringstream&>(const_cast<std::ostringstream&>(std::ostringstream()) << args).str()


Note that ostringstream is type alias for std::basic_ostringstream, gives you something to think about [smile].

Quote:Original post by bjogio
((std::ostringstream()) << args ).str()
you create a new temporary ostringstream everytime? I have to benchmark in order to see if your solution is quicker than mine. I think that it's better to take a central ostringstream and resetting it everytime new data comes. If I remember well in Carmack code there is something like the char* va(char*, ...) function that returns newly formatted string from an array of 3 buffer. I use a C++ style same solution.


Quote:Original post by 255
Good point bjogio. However, that would not work in a threaded program; you'd have to make a mechanism to get thread-specific objects.
OTOH how fast does error reporting really have to be?


#include <boost/pool/pool_alloc.hpp>#include <sstream>typedef boost::fast_pool_allocator<char> char_alloc;typedef std::basic_ostringstream<char, std::char_traits<char>, char_alloc > fast_ostringstream;


Does that make you guys abit more happy now [smile]

This topic is closed to new replies.

Advertisement