0

I am trying to make a logic system using defines macros for implementation of my logger that will expand into nothing when certain toggles are defined. The problem is that when I stack multiple of these switches one nested inside the other (like with calling of the IF_SWITCH_1 function-like macro) I get multiple errors as listed in the code block. What causes these errors? How could I fix them?

    //Creation of the switches
    #define _ADD_PARTS2(part1, part2, ...) part1 ## part2 (__VA_ARGS__)
    #define _ADD_PARTS(part1, part2, ...) _ADD_PARTS2(part1, part2, __VA_ARGS__)
    #define _LOGIC_SWITCH_(name, cond, ...) _ADD_PARTS(name, cond, __VA_ARGS__)
    
    //Toggles
    #define CONDITION_1 true
    #define CONDITION_2 true
    
    //Switches
    #define IF_SWITCH_2_true(a, b, c) std::cout << "Passed" << std::endl
    #define IF_SWITCH_2_false(...)
    
    #define IF_SWITCH_2(a, b, c) _LOGIC_SWITCH_(IF_SWITCH_2_, CONDITION_1, a, b, c)
    
    #define IF_SWITCH_1_true(a, b, c) IF_SWITCH_2(a, b, c)
    #define IF_SWITCH_1_false(...)
    
    #define IF_SWITCH_1(a, b, c) _LOGIC_SWITCH_(IF_SWITCH_1_, CONDITION_1, a, b, c)
    
    //Use
    IF_SWITCH_2(1, 1, 1); //Compiles and passes
    IF_SWITCH_1(1, 1, 1); //"IF_SWITCH_2" was not declared in this scope; 
    //_LOGIC_SWITCH_ was not declared in this scope; 
    //Use of undeclared indentifier IF_SWITCH_2_
    
    //Switching on and off
    #undef CONDITION_2 
    #define CONDITION_2 false //Any invocation from this point on wont pass past the logic switch
    IF_SWITCH_2(1, 1, 1); //Wont pass

As far as I know changing the order of the definitions had no impact on the errors.

Compiled with MinGW 8.10 64-bit

  • So why not just `#if CONDITION_2 ..... #else .... #endif` ? – KamilCuk Aug 13 '20 at 21:09
  • I want to be able to toggle these switches for parts of code ie. `#undef CONDITION_1` and `#define CONDITION_1 false` which will turn all of the switches in the implementation of the macro off from that point on. `#if` cannot be implemented like that. – Christopher Jakubek Aug 13 '20 at 21:18
  • So then why not just `#define IF_SWITCH_1(a, b, c) (CONDITION_1 ? std::cout << "Passed" << std::endl : (void)0)`? Still, macro expandin to nothing is _very confusing_ with loops and `if`s, ie `while (true) IF_SWITCH_1(1, 1, 1)` - when `IF_SWITCH_1` expands to nothin, it will loop over the next statement... – KamilCuk Aug 13 '20 at 21:19
  • The problem is that the logic asosieted with the logging is way more complex then the snipped showed here (log severity, flags for what to log etc.). This combined with the frequency of usage will I fear really lengthen the compile times (especially when compiling for debug mode). Because of that I would prefer if the macros could really expand into nothing even though `(true == true)` statements could work as well and *shouldn't* (what about debug mode?) have any impact on the performance – Christopher Jakubek Aug 13 '20 at 21:34
  • This doesn’t address the question, but names that begin with an underscore followed by a capital letter (`_ADD_PARTS2`, etc.) and names that contain two consecutive underscores are reserved for use by the implementation. Don’t use them in your code. – Pete Becker Aug 13 '20 at 21:47

1 Answers1

0

What causes these errors?

The expansions are run kind-of once. Once a macro is expanded, it wont be expanded again, also in a nested macro calls. Anyway the chain is:

IF_SWITCH_1(1, 1, 1)
_LOGIC_SWITCH_(IF_SWITCH_1_, CONDITION_1, 1, 1, 1)  // step 2
_ADD_PARTS(IF_SWITCH_1_, true, 1, 1, 1)
_ADD_PARTS2(IF_SWITCH_1_, true, 1, 1, 1)
IF_SWITCH_1_true(1, 1, 1)
IF_SWITCH_2(1, 1, 1)
_LOGIC_SWITCH_(IF_SWITCH_2_, CONDITION_1, 1, 1, 1)  
// _LOGIC_SWITCH_ was expanded at step #2
_LOGIC_SWITCH_(IF_SWITCH_2_, true, 1, 1, 1)  
// expanding stops here

How could I fix them?

First of all identifiers with leading underscore followed by uppercase letter are reserved for implementation. Do not use such identifiers in your own code.

If you wish for runtime evaluation, I suggest for:

static inline void if_switch_2_execute(int a, int b, int c) {
    std::cout << "passed" << std::endl;
}
#define IF_SWITCH_2(a, b, c)  (CONDITION_2?if_switch_2_execute(a, b, c):(void)0)
#define IF_SWITCH_1(a, b, c)  (CONDITION_1?IF_SWITCH_2(a, b, c):(void)0)

Anyway, the fix is to move the evaluation of _LOGIC_SWITCH_ from the nested call within _LOGIC_SWITCH_ to upper level, so that _LOGIC_SWITCH_ is expanded only once within one evaluation chain (I do not know how to explain it, that's how I understand it... :/ ). That's why it's typical to do #define MACRO(something) CHOOSE_FUNCTION_TO_CALL(__VA_ARGS__)(__VA_ARGS__).

#define CONDITION_1 true
#define CONDITION_2 true

#define CONCAT(a, b) a##b
#define XCONCAT(a, b) CONCAT(a, b)   // this is your _LOGIC_SWITCH_

#define IF_SWITCH_2_true(a, b, c)  std::cout << "Passed" << std::endl
#define IF_SWITCH_2_false(...)
#define IF_SWITCH_2(a, b, c) XCONCAT(IF_SWITCH_2_, CONDITION_2)(a, b, c)

#define IF_SWITCH_1_true(a, b, c) IF_SWITCH_2(a, b, c)
#define IF_SWITCH_1_false(...)
#define IF_SWITCH_1(a, b, c)  XCONCAT(IF_SWITCH_1_, CONDITION_1)(a, b, c)
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Thanks for the answer. Still would like to know why that is but I think this will point me the right duration. – Christopher Jakubek Aug 13 '20 at 21:47
  • `why that is`, well, why is what? `_LOGIC_SWITCH_` is expanded in `IF_SWITCH_1`. So the _second time_ `_LOGIC_SWITCH_` from `IF_SWITCH_2` will not be expanded, because it already was expanded once. Each macro is expanding only once in one chain. It's the famous ["marked blue"](https://en.wikipedia.org/wiki/Painted_blue) technical term for macro expansions. Anyway, [6.10.3.4p2](https://port70.net/~nsz/c/c99/n1256.html#6.10.3.4p2): `Furthermore, if any nested replacements encounter the name of the macro being replaced, it is not replaced` – KamilCuk Aug 13 '20 at 22:17
  • This answers that. Thanks – Christopher Jakubek Aug 13 '20 at 22:20