15

I am wondering about the following situation:

void f(int a, int b) { }

int a(int x) { std::cout << "func a" << std::endl; return 1; }
int b(int x) { std::cout << "func b" << std::endl; return 2; }

int x() { std::cout << "func x" << std::endl; return 3; }
int y() { std::cout << "func y" << std::endl; return 4; }

f(a(x()), b(y()));

After reading http://en.cppreference.com/w/cpp/language/eval_order I am still having difficulty to understand whether the following evaluation order is possible:

x() -> y() -> a() -> b()

or that the standard guarantees that a(x()) and b(y()) will be evaluated as units, so to speak.

In other words, is there any possibility this will print

func x
func y
func a
func b

Running this test on GCC 5.4.0 gives the to my mind more logical

func y
func b
func x
func a

but this of course does not tell me anything about what the standard requires. It would be nice to get a reference to the standard.

Moos Hueting
  • 650
  • 4
  • 14
  • 1
    I went ahead and gave a more descriptive title for your question. Feel free to change it if you have any problems with that one. – chris Aug 30 '16 at 23:15
  • 1
    Thanks chris and panta rei for the helpful edits. – Moos Hueting Aug 30 '16 at 23:16
  • 1
    The evaluation of function *arguments* is not related to the evaluation of the function. The bit about "evaluated as a unit" only refers to the evaluation of the function - the evaluations of `a(...)` and `b(...)` do not interleave. – Kerrek SB Aug 30 '16 at 23:37

2 Answers2

14

In C++14 and earlier, x -> y -> a -> b is possible. The sequencing relations here are:

  • Call to x is sequenced before call to a.
  • Call to y is sequenced before call to b.
  • Call to a is sequenced before call to f.
  • Call to b is sequenced before call to f.

There are no other restrictions on the order. If you want to enforce some particular ordering then you'll have to break this call up into multiple full-expressions.

In the C++14 standard this intent is clarified by the note [expr.call]/8:

[Note: The evaluations of the postfix expression and of the arguments are all unsequenced relative to one another. All side effects of argument evaluations are sequenced before the function is entered. —end note ]

As noted in comments, the cppreference page lists some more sequencing rules marked as "since C++17". This is based on n4606, the latest published draft for C++17. So it is possible that for C++17, this order will no longer be allowed.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • 9
    +1 This is why we use `make_unique(args)` instead of `new T(args)`; in the latter case, `func(unique_ptr(new T(args)), new unique_ptr(new U(args)))` could leak. – GManNickG Aug 30 '16 at 23:16
  • 2
    @GManNickG good point, I hadn't considered make_unique from that angle – M.M Aug 30 '16 at 23:16
  • 1
    Could you elaborate on these points from the linked [Order of evaluation](http://en.cppreference.com/w/cpp/language/eval_order): "*evaluations of A and B are indeterminately sequenced: they may be performed in any order but **may not overlap**: either A will be complete before B, or B will be complete before A. [...] 15) In a function call, value computations and side effects of the initialization of every parameter are **indeterminately sequenced** with respect to value computations and side effects of any other parameter.*". That appears to say that `a(x()))` and `b(y())` can *not* overlap. – dxiv Aug 30 '16 at 23:21
  • 1
    Thanks M.M, and the others for the discussion. – Moos Hueting Aug 30 '16 at 23:36
  • 1
    Thanks for pointing that. Still, N3337 (which is C++11) 5.2.2/4 says something very similar: "*When a function is called, each parameter (8.3.5) shall be initialized (8.5, 12.8, 12.1) with its corresponding argument. [ Note: **Such initializations are indeterminately sequenced with respect to each other** (1.9) — end note ]*" unless it refers, as you suggested, strictly to the *initialization* of the parameter *after* its value was evaluated. – dxiv Aug 30 '16 at 23:38
  • 2
    @dxiv in that situation I believe "argument" refers only to the final result of evaluating the argument expression; the "atomic" unit here is the conversion between that result and the parameter which may have a different type to the argument and therefore involve a conversion sequence. – M.M Aug 30 '16 at 23:43
  • 2
    Current C++17 wording, which cppreference is based on, is `5.2.2[expr.call]p5` of [n4606](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4606.pdf) – Cubbi Aug 31 '16 at 03:22
  • 1
    @Cubbi I guess they revisited the issue at a subsequent meeting than that mentioned in p0145r3. I read that section in 4606 to still say that the sequencing is unchanged for OP's case; with the text "initialization of a parameter" excluding the evaluation of the argument expressions as it did in previous editions. – M.M Aug 31 '16 at 03:54
  • @Yakk: Whoops. :) – GManNickG Aug 31 '16 at 04:05
  • 1
    @M.M if you exclude the initializer from initialization, what's left to have "value computations and side effects"? – Cubbi Aug 31 '16 at 04:08
  • 1
    @Cubbi I'm excluding the argument expression, not the initializer. Those effects apply to the process of using the *result* of the argument expression to initialize the parameter. This could involve a conversion sequence. – M.M Aug 31 '16 at 04:09
  • 1
    @M.M earlier in the same section "each parameter shall be initialized with its corresponding argument" and earlier still "A function call is a postfix expression followed by parentheses containing a possibly empty, comma-separated list of initializer-clauses which constitute the arguments to the function." The initializer is the argument expression. – Cubbi Aug 31 '16 at 04:14
  • 1
    @Cubbi OK. I think it's not clearly specified in the standard; but as dxiv pointed out, C++11 uses the same terminology and it is commonly accepted that in C++11 that the sequencing is as given in my answer. The use of *indeterminately sequenced* (in my understanding) would be revealed by something like `void f( P p, Q q ); A a, b; f( a, b );`. The conversion of `a` to a temporary `P` and then invocation of `p`'s copy/move-constructor with the temporary, cannot be interleaved with the same process for `q`. – M.M Aug 31 '16 at 04:25
  • 1
    Okay, I now think you're right about 5.2.2. The line that was removed in C++17 "The evaluations of the arguments are all unsequenced relative to one another" did refer directly to the arguments. The note right after the new sentence in 5.2.2/5 that talks about initializations refers to argument evaluations, though, so it seemed, as I was reading it, that it clarifies something that is part of the sentence it attaches to. – Cubbi Aug 31 '16 at 11:22
  • 1
    Found why I remembered it differently: p400r0 describes the new sentence about "initialization of a parameter, including every associated value computation and side effect" as "wording describing the indeterminate sequencing of function argument evaluation" – Cubbi Aug 31 '16 at 17:41
  • 1
    @Cubbi OK, have updated my answer to hopefully reflect this comment discussion. (feel free to edit) – M.M Aug 31 '16 at 20:59
  • 1
    I'll take it to std-discussion, this seems important – Cubbi Sep 02 '16 at 14:32
2

Another way to look at it:

There would be no benefit to evaluating both x and y before commencing evaluation of either a or b. In fact, there would be a penalty. An extra intermediate result would have to be temporarily held somewhere which would either require an additional stack push/pop, or consume an additional CPU register (overuse of which would lead to additional stack operations anyway). While it may be of little or no consequence for the example you provided, more complex cases would reveal the inefficiencies.

The rule could be viewed as laziest possible evaluation i.e. not performing evaluations until needed so as to avoid carrying extra temporary results.

Zenilogix
  • 1,318
  • 1
  • 15
  • 31
  • 1
    Well - there is some benefit: most programming languages do have defined order of evaluation from these sort of expressions, which means fewer surprises (both for new coders and experienced coders), and fewer bugs caused due to order of evaluation differing from what the coder expected. Also it is doubtful whether there is any evidence in practice to support the claims about inefficiencies; [see this paper](http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0145r1.pdf), section 7 conclusions especially – M.M Aug 31 '16 at 01:19
  • 1
    I should think that order of expression evaluation would matter only in regard to side-effects, which by "best practice" should be avoided, so nobody gets any surprises no matter the implemented order. – Zenilogix Sep 02 '16 at 23:13
  • 1
    @M.M In fact if you see this ({https://groups.google.com/a/isocpp.org/d/msg/std-discussion/7ylid9Tkgp0/J5eNVm9kBgAJ}) post from a lengthy thread, you'd see a proposal attached that calls for a strict left-to-right order of evaluation. – ForeverLearning Sep 03 '16 at 02:56