56

I was reading that in C++ using macros like

#define max(a,b) (a > b ? a : b)

can result in a 'double evaluation'. Can someone give me an example of when a double evaluation occurs and why it's bad?

P.S.: Surprisingly I couldn't find any detailed explanation when googling for it except for an example in Clojure (which I can't understand).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
AnkithD
  • 743
  • 6
  • 9
  • It refers to `max(1.0, 2.0)`. because the arguments are double, it's "double evaluation". However if you say `max(1, 2)` it is not a double evaluation. Therefore it only "can" result in double evaluation, it doesn't have to – Johannes Schaub - litb Sep 11 '16 at 18:14
  • 23
    I would have interpreted that otherwise: if a and b are complex or function calls they would be evaluated more than once. Not good for performance and may even generate bugs. – Jean-François Fabre Sep 11 '16 at 18:18
  • 5
    What if `a` or `b` were a function call with side effects? What if `a` or `b` were `x++` or `++x`? – James M Sep 11 '16 at 18:19
  • @JohannesSchaub-litb nice remark :) – Edgar Rokjān Sep 11 '16 at 18:20
  • @JamesMcLaughlin but if they mean this, it is always a double evaluation of `a` or `b`. But they say it "can" result in a double evaluation, therefore I think they mean something else and I keep my debatable position on it. – Johannes Schaub - litb Sep 11 '16 at 18:23
  • @JohannesSchaub-litb: It *can* result in a double evaluation of `a`. It *can* result in a double evaluation of `b`. Exactly one of those possible outcomes will always occur, but that doesn't change the fact that each double evaluation is only a possibility. – Ben Voigt Sep 11 '16 at 18:26
  • 1
    Also related to side effects in macro arguments: http://stackoverflow.com/a/18885626/103167 – Ben Voigt Sep 11 '16 at 18:27
  • 1
    @BenVoigt OK, I see now what they mean: If `a` is `((throw 0), 0)`, then surely `a` is only evaluated once and `b` is evaluated once or zero times. You are right, there is a possibility that neither is evaluated twice. – Johannes Schaub - litb Sep 11 '16 at 18:28
  • As a side note: you want to add parentheses around the macro parameters in the expansion, otherwise operator precedence could bite you in the back. – Daniel Jour Sep 11 '16 at 18:32
  • 4
    Consider: `pips = max (rand(), 5) + 1; // rolling a die`. Is this guaranteed to return an integer in [1,6]? Not if `max()` is a macro that involves double evaluation. – njuffa Sep 11 '16 at 19:09
  • @JohannesSchaub-litb, if a is a trivial expression with no side fx, the compiler will only evaluate it once and then reuse its last value hoping he did nothing wrong. So it can be a single evaluation; and we can thus suppose sometimes it can also be a double evaluation...hopefully. – bipll Sep 11 '16 at 19:14
  • 1
    @JohannesSchaub-litb the op simply says "can result in double evaluation" which you are clearly intelligent enough to deduce refers to the fact that the conditional is dependent on use cases, such as `max(i, a.pop_and_ret())` or `max(x++, y++)` which expands to `(x++ > y++ ? x++ : y++)` during *preprocessing* so the optimizer has no clue there was a macro expansion. – kfsone Sep 11 '16 at 19:49
  • I don't understand why I receive so many "protest" here. I simply have chosen an interpretation and it seems I was wrong, as I elaborated in my comment to @BenVoigt's comment. – Johannes Schaub - litb Sep 11 '16 at 19:51
  • @kfsone I don't understand what "the conditional is dependent on use cases" means. Apparently I lack some of the alleged intelligence. – Johannes Schaub - litb Sep 11 '16 at 19:53
  • @JohannesSchaub-litb `max(1,2)` has no double eval, `int x=1, y=2; max(x, y)` has none. The "can" refers to the fact the macro is not robust enough to handle all cases consistently, but is hi model not ub. Thus the conditional (*can* cause double evaluation) is dependent on specific use cases - it definitely doesn't occur for a well defined set of cases but definitely does for the rest. – kfsone Sep 11 '16 at 20:10
  • @kfsone it seems the confusion between us is caused by different understanding of "double evaluation". I understand it as "one expression is evaluated twice" wheras you understand it as "one expression with side effects is evaluated twice". Certainly either could be meant by OP's source. – Johannes Schaub - litb Sep 11 '16 at 21:59
  • @JohannesSchaub-litb Side-effects aren't required, the compiler has to determine that side-effects do *not* occur, see https://godbolt.org/g/GvUMMl, https://godbolt.org/g/T9bpn6. It comes down to the term "can", and the fact that the op is asking *when*; coupled with "why its bad" I'm inclined to read the OP as not being familiar enough with DE to be implying specifics. – kfsone Sep 11 '16 at 22:22
  • @JohannesSchaub-litb, Are you trying to confuse people who find this page later on purpose? Obviously the answer has nothing to do with the type `double`, it has to do with the expressions, a and b, being double evaluated – asimes Sep 12 '16 at 01:23
  • 1
    @asime pleaae stop acccusing me of confuaing peoole. That's offensive – Johannes Schaub - litb Sep 12 '16 at 08:17
  • 1
    Apart from the double evaluation issue, your code also has an operator precedence issue. You should put parentheses around the arguments to prevent that. i.e. `#define max(a,b) ((a) > (b) ? (a) : (b))` – CodesInChaos Sep 12 '16 at 10:08
  • So I suppose 007.0 would be a double double agent? –  Sep 12 '16 at 17:37

4 Answers4

77

Imagine you wrote this:

#define Max(a,b) (a < b ? b : a)

int x(){ turnLeft();   return 0; }
int y(){ turnRight();  return 1; }

then called it like this:

auto var = Max(x(), y());

Do you know that turnRight() will be executed twice? That macro, Max will expand to:

auto var = (x() < y() ? y() : x());

After evaluating the condition x() < y(), the program then takes the required branch between y() : x(): in our case true, which calls y() for the second time. See it Live On Coliru.

Simply put, passing an expression as an argument to your function-like macro, Max will potentially evaluate that expression twice, because the expression will be repeated where ever the macro parameter it takes on, is used in the macro's definition. Remember, macros are handled by the preprocessor.


So, bottom line is, do not use macros to define a function (actually an expression in this case) simply because you want it to be generic, while it can be effectively done using a function templates

PS: C++ has a std::max template function.

WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
  • What is the resolution for the example? Is there a max thingy in the C++ standard library? Depending on the C++ standard version (e.g. C++14)? – Peter Mortensen Sep 12 '16 at 01:08
  • @PeterMortensen There has always been a [`std::max`](http://en.cppreference.com/w/cpp/algorithm/max) template function in C++, though it's been modified to reflect changes in recent C++ version – WhiZTiM Sep 12 '16 at 01:22
  • 2
    @PatrickM'Bongo: Both points appear to be rather meaningless. `std::max(12.f, 13.)` is the normal way to disambiguate this. And no function can extend the lifetime of its arguments, that's not how C++ works. Not that it really matters: the reference doesn't dangle until the end of the full expression anyway. It only matters if you use it to initialize another reference, and at that point you obviously are responsible for your reference. – MSalters Sep 12 '16 at 08:59
  • @MSalters You do realize this is trivial to fix by just having `max` be a variadic template taking forwarding references instead of the nonsense we currently have, right? – Griwes Sep 12 '16 at 09:05
  • @PatrickM'Bongo: Considering that you might return either argument as a `T`, it is pretty obvious that both should be convertible to that type `T`. – MSalters Sep 12 '16 at 09:10
  • 2
    @MSalters No, in the sane case you return as `common_type_t`, because that's free with `?:`. – Griwes Sep 12 '16 at 09:11
  • 1
    Note in C gcc and clang support [an extension called statement expressions which can be used to avoid](http://stackoverflow.com/a/18885626/1708801) the double evaluation issue. In C++ we have other techniques available. – Shafik Yaghmour Sep 12 '16 at 15:31
  • As a side note, this definition of max either as a macro or template can have [other side effects](http://stackoverflow.com/a/30915238/2542702) for floating point. – Z boson Sep 13 '16 at 09:24
24

a and b occur two times in the macro definition. So if you use it with arguments that have side-effects, the side-effects are executed two times.

max(++i, 4);

will return 6 if i = 4 before the call. As it is not the expected behavior, you should prefer inline functions to replace such macros like max.

Franck
  • 1,635
  • 1
  • 8
  • 11
  • 4
    Also, this is **not** an undefuned behaviour `((++i) < (4) ? (++i) (4))`, because there is a [sequence point](https://en.wikipedia.org/wiki/Sequence_point) after evaluating first `++i`. However, using several increments on the same variable on the same line is probably not a good idea in general. – Daerdemandt Sep 12 '16 at 18:14
20

Consider the following expression:

 x = max(Foo(), Bar());

Where Foo and Bar are like this:

int Foo()
{
    // do some complicated code that takes a long time
    return result;
}

int Bar()
{
   global_var++;
   return global_var;
}

Then in the original max expression is expanded like:

 Foo() > Bar() ? Foo() : Bar();

In either case, Foo or Bar is going to executed twice. Thereby taking longer than necessary or changing the program state more than the expected number of times. In my simple Bar example, it doesn't return the same value consistently.

selbie
  • 100,020
  • 15
  • 103
  • 173
8

The macro language in C and C++ is processed by a dedicated parser at the 'pre-processing' stage; the tokens are translated and the output is then fed into the input stream of the parser proper. #define and #include tokens are not recognized by the C or C++ parsers themselves.

This is important because it means that when a macro is said to be "expanded" it means literally that. Given

#define MAX(A, B) (A > B ? A : B)

int i = 1, j = 2;
MAX(i, j);

what the C++ parser sees is

(i > j ? i : j);

However if we use the macro with something more complex, the same expansion happens:

MAX(i++, ++j);

is expanded to

(i++ > ++j ? i++ : ++j);

If we pass something that makes a function call:

MAX(f(), g());

this will expand to

(f() > g() ? f() : g());

If the compiler/optimizer can demonstrate that f() has no side-effects, then it will treat this as

auto fret = f();
auto gret = g();
(fret > gret) ? fret : gret;

If it can't, then it will have to call f() and g() twice, for example:

#include <iostream>

int f() { std::cout << "f()\n"; return 1; }
int g() { std::cout << "g()\n"; return 2; }

#define MAX(A, B) (A > B ? A : B)

int main() {
    MAX(f(), g());
}

Live demo: http://ideone.com/3JBAmF

Similarly if we were calling an extern function, the optimizer may not be able to avoid calling the function twice.

kfsone
  • 23,617
  • 2
  • 42
  • 74