0

I have the following macro in a logging library of mine :

#define TRACE_E(__logCat,__format,...) \
    do { \
        ::dbg::LogCategory * const __catPtrVal = (::dbg::LogCategory *)(__logCat); \
        if( NULL != __catPtrVal && __catPtrVal->IsEnabled() ) \
        { \
            __catPtrVal->Error( __format, __VA_ARGS__ ); \
        } \
    } while( false )

Under Visual Studio (2008) it works as intended, i.e i can do both TRACE_E( pLog, "some message without parameters" ); and TRACE_E( pLog, "some message with parameters %d %d", 4, 8 );

But when using this same library with eclipse and the Android NDK i'm getting a compilation error if i don't pass at least one parameter after the format string in my macro, i.e TRACE_E( pLog, "some message without parameters" ); is not valid, but TRACE_E( pLog, "some message without parameters", 0 ); is, which forces me to pass a dummy parameter when none is needed.

Is there any difference of behaviour with variadic macros when using g++ rather than Visual Studio's compiler ? Thank you.

Virus721
  • 8,061
  • 12
  • 67
  • 123
  • What causes you to write this as a macro? – JBL Apr 10 '15 at 09:30
  • I used a macro which is only defined in debug mode. This allows parameters not to be computed when not in debug mode, which is not possible when using a real function, at least as far as i know. – Virus721 Apr 10 '15 at 09:46

1 Answers1

4

Yes. What you are attempting is not possible in standard C or C++.

This is arguably a defect in the respective standards for which different compilers have different workarounds. Visual Studio tries to make it work as-is, gcc and clang require the following syntax:

__catPtrVal->Error( __format, ##__VA_ARGS__ );

This is described here for gcc; clang just adopted gcc's way of doing things. Unfortunately, MSVC does not understand this syntax. There is, to my knowledge, no portable way of solving this in the general case.

For your particular macro, though, you could simply write

#define TRACE_E(__logCat,...) \
    do { \
        ::dbg::LogCategory * const __catPtrVal = (::dbg::LogCategory *)(__logCat); \
        if( NULL != __catPtrVal && __catPtrVal->IsEnabled() ) \
        { \
            __catPtrVal->Error(__VA_ARGS__ ); \
        } \
    } while( false )

Since the only place where you use __format is directly before __VA_ARGS__.

Side note: You're using a lot of reserved identifiers there. Unless you're writing a standard library implementation, you should go easier on the underscores.

Wintermute
  • 42,983
  • 5
  • 77
  • 80
  • Thanks for your help. You say that MSVC doesn't understand `##__VA_ARGS__`, but what if i add a dummy define before, such as : `#define DUMMY_DEFINE`and `DUMMY_DEFINE##__VA_ARGS__`. Can this work ? Also this solution you provide seems to add more "unsafeness", but if this is the only option i should probably go for that. And what reserved identifiers am I using ? I added underscores to try avoiding identifier clashes. – Virus721 Apr 10 '15 at 09:29
  • The `DUMMY_DEFINE` wouldn't be expanded in that context; if `__VA_ARGS__` is `a, b, c` you'd end up with a list `DUMMY_DEFINEa, b, c` (unless `DUMMY_DEFINEa` is also a macro, in which case it'll be expanded). The precise rules for reserved identifiers are behind the link; the one you're breaking is that identifiers that contain `__` are reserved for the implementation (meaning the compiler and standard library). You're not supposed to use them to avoid identifier clashes with the implementation. – Wintermute Apr 10 '15 at 09:38
  • By the way, have you considered writing this as a variadic function template? – Wintermute Apr 10 '15 at 09:39
  • Thanks. I used a macro which is only defined in debug mode. This allows parameters not to be computed when not in debug mode,; which is not possible when using a real function, at least as far as i know. – Virus721 Apr 10 '15 at 09:42
  • I suppose that makes sense; the optimizer would not always be smart/free enough. I might have suggested implementing it as a variadic function template and having a macro that expands to the function template only in debug builds to get the best of both worlds, but I don't think there's anything to gain from that in this particular case. Except perhaps type safety for the `__logCat` parameter, but judging from the macro body, type safety for the `__logCat` parameter is not a priority. :P – Wintermute Apr 10 '15 at 09:54
  • I'll think about this idea. I'm implementing the suggestion from your answer, but i'm stuck on something. My macro calls the function `LogCategory::Error` which is : `void LogCategory::Error( ... ) { /*...*/ va_list args; va_start( args, ??? ); m_pLog->Error( this, sFormat, args ); } sFormat, args );` The problem is that i can't initialize the va_list since i removed the format argument ! What can I do ? Should i create an overload to that function that expects a va_list ? – Virus721 Apr 10 '15 at 09:59
  • `LogCategory::Error` can still be `void LogCategory::Error(char const *fmt, ...)`. – Wintermute Apr 10 '15 at 10:01
  • Mmm seems to make sense. Thank you. – Virus721 Apr 10 '15 at 10:02
  • It's probably worth mentioning that recent versions of gcc are more strict about enabling these extensions than before. For example, '-std=c++11' will not enable the extensions on the gcc 4.9 that currently ships with the Android NDK. (I had to change it to '-std=gnu++11'.) Quite confusing when you're using code that used to work with gcc and still does with clang. :/ – Tyler Daniel Jan 12 '16 at 03:59