0

If in a 'logger.h' file I have this:

template <int CompileTimeVerbosity>
struct Logger
{
    static inline int compileTimeVerbosity = CompileTimeVerbosity;
    void log(const std::string& msg) { }
};

#define LOG(logger, msgVerbosity, msg) \
if constexpr (msgVerbosity >= logger.compileTimeVerbosity) \
{ \
logger.log(msg); \
} \

inline Logger<4> loggerObj;

I include this header in a bunch of .cpp files. Then in one of them I do:

void func()
{
    LOG(loggerObj, 2, std::string("Sorry, you called a ") + std::to_string(int()) + " on a " + std::to_string(int()))
}

Because the inline Logger<4> loggerObj is instructed to be defined only once and we include the header in multiple .cpp files, we don't know where it has been defined. We only know that the compiler defines it once in 'some' .cpp. Therefore when we call func() in some .cpp the 'loggerObj' identifier is probably akin to something like 'extern Logger<4> loggerObj;'. If this is the case, am I right in saying that the compiler doesn't have its full definition at that point? Also that would mean that the everything in the func() function will be compiled, and then only the linker can do the if constexpr statement against the object that's probably defined in another translation unit.

I'm not sure if this is a link-time optimization, but what's in the LOG() macro invocation should be easily optimized out by the linker, right? In other words the code generated shouldn't contain std::string() + std::string() etc.?

Unreal Engine has logging system similar to this but I think it may work completely at the macro level, so in macro invocation UELog(category, verbosity, msg) I think the entire line is just elided by the preprocessor instead of copying something like if constexpr.

Is there a way I can do this? I can't find the source code for this part of the logging system.

Zebrafish
  • 11,682
  • 3
  • 43
  • 119
  • *"linker can do the if constexpr statement"* - no, `if constexpr` will be evaluated during compilation step – user7860670 May 18 '21 at 07:23
  • `if constexpr` is *not* the right tool for this. It is not at all like an `#if`. – StoryTeller - Unslander Monica May 18 '21 at 07:41
  • @user7860670 Really? Is that because it knows the template type from extern Logger<4> logger1; ? Or could it do it in any case? Because I thought the compiler compiles what's in a single translation unit and the linker does the rest, hence why I get a linker error, not a compiler error when a variable marked as extern is not found. – Zebrafish May 18 '21 at 07:47
  • @StoryTeller-UnslanderMonica They both eliminate that piece of code if 'verbosityLevel' is lower, it's just that one eliminates it from that source code at the preprocessor step, and the other eliminates it from the compiled code at the compiler step, right? – Zebrafish May 18 '21 at 07:50
  • No. That's a common misconception. https://stackoverflow.com/q/46512248/817643 – StoryTeller - Unslander Monica May 18 '21 at 07:59
  • @StoryTeller-UnslanderMonica So at what part of the compilation process is that part of the code in the constexpr if eliminated? – Zebrafish May 18 '21 at 08:19
  • @Zebrafish - Only inside templates. And only when depending on a template parameter. Neither of which is applicable to how you use `LOG`. – StoryTeller - Unslander Monica May 18 '21 at 08:20
  • @StoryTeller-UnslanderMonica Yeah I get how the code inside the if constexpr is still evaluated by the compiler, but at what stage is that code eliminated? The statement after if (false) isn't going to find its way into your code, right? – Zebrafish May 18 '21 at 08:42
  • @Zebrafish - Who says? There is no specification that says it must. It's a pure QoI message. And as an aside, I won't be surprised if compilers will start warning about usage outside of templates. – StoryTeller - Unslander Monica May 18 '21 at 08:48
  • @StoryTeller-UnslanderMonica So is my expectation that in 'if (false) { /* Do something*/ }' that /*Do something*/ code will be eliminated as a compiler optimization an unreasonable one? Are you saying only using macros can guarantee that that's optimized out and doesn't find its way into the generated code? If so I need to find a way to do this through only macros. – Zebrafish May 18 '21 at 09:03
  • I'm saying there is a difference between being optimized out (which is likely, but happens later) and being discarded entirely (which happens early). If the former works for you, then go with it (and even a regular `if` will do). But if you need the latter, your approach will not accomplish it. – StoryTeller - Unslander Monica May 18 '21 at 09:08

1 Answers1

3

Your solution is almost correct. It doesn't compile because compileTimeVerbosity is not constexpr, but if you make it constexpr replacing

static inline int compileTimeVerbosity = CompileTimeVerbosity;

with

static constexpr int compileTimeVerbosity = CompileTimeVerbosity;

then it does what you want: https://godbolt.org/z/azecWMY86.

As a side note, never make a macro that expands into if (...) { }. Loot at this example:

if (...)
  LOG(...)
else
  ...

You'd expect else branch refers to the outer if, but in reality if inside the macro hijacks it.

The way to fix this is to always wrap macro code in do { ... } while (false), e.g.:

#define LOG(logger, msgVerbosity, msg) \
  do { if constexpr (...) { ... } } while(false)

This loop executes exactly once and it doesn't have nasty gotchas.

Andrei Matveiakin
  • 1,558
  • 1
  • 13
  • 17
  • 1
    On the side note: the better rule is to always use `{ }` after `if`, also when there is just a single statement. That rule avoids a whole range of potential problems including the case above of having a macro expanding into `if (...) { }`. – nielsen May 18 '21 at 07:58
  • I'm all for always using `{ }` after `if`. I'd rather disagree that this rule is “better”, but this is beside the point. Use both. When it comes to protecting yourself from shooting in the foot with C++ macro two levels of precaution doesn't seem like too much precaution to me :) – Andrei Matveiakin May 18 '21 at 08:15
  • @AndreiMatveiakin Instead of putting the do/while in the macro, I can add the extra parentheses around it, can't I, simulating someone putting extra parentheses in the first outer if statement? – Zebrafish May 18 '21 at 09:56
  • @Zebrafish That would be better, but still not perfect. If the user thinks of `LOG` as a sort-of-like-a-function they would habitually add semicolon after it: `if (...) LOG(...); else ...`. However `if (...) { ... }; else ...` wouldn't compile. If you want `LOG` to behave syntactically like a function in all possible contexts, then `do { ... } while (false)` is the most reasonable way to achieve that AFAIK. – Andrei Matveiakin May 18 '21 at 10:29