2

Are the two below constructions equivalent in assumption that "cond" is not conflicting with any name in the program

#define DOWHILE do { (some_code); } while (0)

#define FORLOOP for(bool cond = true; cond; cond = false) (some_code)

The purpose of this question is:

I have something like this

bool printLogs; // nonconstant dynamic variable

And I have a macro(I cannot do big changes, it is a big project; I have to deal with this macro) #define LOG ... which is used like

LOG << "message" << 1 << 0.5 << 'a';

And I want this macro to turn into

if (printLogs) {
    PrinterClass() << "message" << 1 << 0.5 << 'a';
}

So the printed arguments are not calculated if not printed. In this case my solution is

#define LOG for(cond = printLogs; cond; cond = false) PrinterClass()

Is this solution correct? Are there any other ways?

UPDATE: Obviouse you cannot use a simple if here. For example this code won't work

#define LOG if(printLogs) PrinterClass()

int main() {
    if (1)
        LOG << 1;
    else
        LOG << 2;
}

UPDATE 2: I expect to see the explanation of the correctness of my or your solution. I must be sure that solution would not cause any problems. You can insert the "do while" construction anywhere in your code where you can insert a statement. So "do while" behaves as a simple statement. Is that true for my construction?

UPDATE 3: The solution with global object does not satisfy as it will cause a huge overhead

#include <atomic>
void printImpl(...);

std::atomic<bool> printLog;

struct Log {
    template <typename T>
    Log& operator<<(const T& t) {
        if (printLog) { 
            printImpl(t);
        }
        return *this;
    }
};

int main() {
   Log() << 1 << 2;
}

After all optimizations will turn into

int main() {
    if (printLog) {
        printImpl(1);
    }
// Still should check because printImpl could have changed this variable.
// C++ does not provide any way to say "I promise it won't change printLog"
    if (printLog) { 
        printImpl(2);
    }
}

So you have atomic comparison for each use of <<. See https://godbolt.org/z/sEoUCw

SaveMyLife
  • 85
  • 6
  • @walnut: See this: https://stackoverflow.com/q/154136/10077 – Fred Larson Dec 11 '19 at 14:31
  • Seems like it would be easier to just have a `LOG` be a global object which contains the condition `printLogs` as part of it's state. – François Andrieux Dec 11 '19 at 14:39
  • @FredLarson I see, but this isn't directly the same form asked for here. – walnut Dec 11 '19 at 14:41
  • @FrançoisAndrieux But then `LOG << someFunction();` would call the function even if the log is disabled. – interjay Dec 11 '19 at 14:42
  • @interjay If the condition is known at compile time you can arrange for it to be removed at compilation. For example, if you make the condition a template argument you can specialize `LOG`'s type so that `operator<<` is a NOP when you don't want to log. Any half way decent optimizer will remove calls to NOP functions. – François Andrieux Dec 11 '19 at 14:43
  • @FrançoisAndrieux Sure, but we have no reason to believe that the condition is known at compile time. – interjay Dec 11 '19 at 14:45
  • I'm sure the best ways to create a logger stream have been well covered on this site and elsewhere already – Lightness Races in Orbit Dec 11 '19 at 14:46
  • @interjay I've edited my comment, because you are right that the behavior is not entirely known at compile time. – François Andrieux Dec 11 '19 at 14:46
  • *"I must be sure that solution would not cause any problems in the same way that the "do while" trick does."* `do {...} while(0)` is only necessary when you have several statements in one macro. Your macro only contains a single incomplete statement. – HolyBlackCat Dec 11 '19 at 15:29
  • @FrançoisAndrieux for the case of global object see "update 3" in my question – SaveMyLife Dec 11 '19 at 15:44
  • @HolyBlackCat I mean you can insert that `do while` construction anywhere in your code and it will not cause you any problems. I need to be sure that the same holds true for my construction – SaveMyLife Dec 11 '19 at 15:46
  • @FrançoisAndrieux I gave an explanation why compiler is NOT allowed to do this optimization. This optimization is NOT correct. If you still do not believe you could follow the last link in the "update 3" – SaveMyLife Dec 11 '19 at 15:52
  • @SaveMyLife While I'm not convinced that this optimization can't happen, you're right that it doesn't here. If you could point to a language rule that makes the optimization illegal, I would be very curious to read it. But either way, have you considered using a function call syntax instead? Then you only have to check the branch once. Edit : For example https://godbolt.org/z/-mNr3M – François Andrieux Dec 11 '19 at 16:05
  • @FrançoisAndrieux 1) This optimization is illegal because compiler does not see the body of the function, which could contain `printLogs = !printLogs;`. While you assume that `printImpl` does not do this, the compile cannot prove this, it sees only the declaration. Thus for the compiler `printImpl` is a function that could change ANY(even constants) variables, including `printLogs`. Hence the check is needed after EVERY `printImpl` call. – SaveMyLife Dec 11 '19 at 16:33
  • @FrançoisAndrieux 2) as for your function solution -- by all means it is better, except for it is not compatible with the old version. Since I'm not allowed to break compatibility, your solution does not fit my case. – SaveMyLife Dec 11 '19 at 16:35
  • @SaveMyLife My false assumption was that the definition of `printImpl` was visible to the compiler and the missing definition was just for brevity. If in fact it's not visible then you are entirely correct. – François Andrieux Dec 11 '19 at 16:44

2 Answers2

2

You can do it like this:

#define LOG if (!printLogs){} else PrinterClass()
Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • 1
    `if (1) LOG << 1; else LOG << 2;` would be broken – SaveMyLife Dec 11 '19 at 14:37
  • @SaveMyLife why would that be broken? – Aykhan Hagverdili Dec 11 '19 at 14:46
  • 2
    @Ayxan: Because the `else ` would go with the `if` in the macro instead of the `if` before the macro. – Fred Larson Dec 11 '19 at 14:47
  • How is that different from my solution? It is still unclear why this would not cause any problems – SaveMyLife Dec 11 '19 at 15:04
  • @SaveMyLife: I'd say it's no less effective than your `for` loop solution, and probably a bit clearer. By negating the condition and putting the work in the `else`, it eliminates the `else` problem of the naive `if` in the macro. – Fred Larson Dec 11 '19 at 15:11
  • @FredLarson the main part of the question is the correctness of the macro and the problems it could cause. This solution is does not differ from mine in that sense – SaveMyLife Dec 11 '19 at 15:13
  • I would undelete your other answer. You may be right about having a member `bool` enabling the optimization that a global atomic `bool` prevents. – François Andrieux Dec 11 '19 at 16:59
  • @FrançoisAndrieux looks like almost all compilers inline the function call but non optimizes the `if (active)` out. I wonder why https://godbolt.org/z/4kKH5N – Aykhan Hagverdili Dec 11 '19 at 17:55
0

If you want an object-oriented solution without the overhead of multiple checks, consider something like this:

#include <atomic>
#include <utility>
void printImpl(...);

std::atomic<bool> printLog;

class Log {
 public:
  template <typename T>
  const auto& operator<<(T&& t) {
    if (printLog) {
      ulog.active = true;
      return ulog << std::forward<T>(t);
    } else {
      ulog.active = false;
      return ulog;
    }
  }

 private:
  struct unchecked_log {
    template <typename T>
    const auto& operator<<(T&& t) const {
      if (active) {
        printImpl(std::forward<T>(t));
      }
      return *this;
    }
    bool active{false};
  };
  unchecked_log ulog{};
};

// Instead of the macro. Doesn't break backward compatibility
Log LOG;

void test(bool) { LOG << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10; }
Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • This will definitely work. Thank you. I'm still interested in the correctness of your first solution. If you have any ideas feel free to extend this answer :) – SaveMyLife Dec 11 '19 at 16:43
  • It doesn't actually work as written. Only the first argument is ignored in the `<<` chain if the logging condition is false. You would need to return a non-printing stream object when the condition is false, and then the return type is not known at compile time. – François Andrieux Dec 11 '19 at 16:48
  • @FrançoisAndrieux you're right. Let me see how I can fix it. – Aykhan Hagverdili Dec 11 '19 at 16:49
  • @FrançoisAndrieux thank you! Actually I do not see any way it could be fixed – SaveMyLife Dec 11 '19 at 16:52
  • 1
    Related question : https://stackoverflow.com/questions/59291618/is-this-a-missed-optimization-opportunity-or-not – François Andrieux Dec 11 '19 at 18:11