How to Guarantee Order of Argument Evaluation When Calling a Function Object

How to guarantee order of argument evaluation when calling a function object?

What about a silly wrapper class like this:

struct OrderedCall
{
template <typename F, typename ...Args>
OrderedCall(F && f, Args &&... args)
{
std::forward<F>(f)(std::forward<Args>(args)...);
}
};

Usage:

void foo(int, char, bool);

OrderedCall{foo, 5, 'x', false};

If you want a return value, you could pass it in by reference (you'll need some trait to extract the return type), or store it in the object, to get an interface like:

auto x = OrderedCall{foo, 5, 'x', false}.get_result();

How not specify an exact order of evaluation of function argument helps C & C++ compiler to generate optimized code?

I think the whole premise of the question is wrong:

How not specify an exact order of evaluation of function argument helps C & C++ compiler to generate optimized code?

It is not about optimizing code (though it does allow that). It is about not penalizing compilers because the underlying hardware has certain ABI constraints.

Some systems depend on parameters being pushed to stack in reverse order while others depend on forward order. C++ runs on all kinds of systems with all kinds on constraints. If you enforce an order at the language level you will require some systems to pay a penalty to enforce that order.

The first rule of C++ is "If you don't use it then you should not have to pay for it". So enforcing an order would be a violation of the prime directive of C++.

Function argument evaluation order

In this case it does not matter.

By passing szBuffer to a function that accepts a char * (or char const *) argument, the array decays to a pointer. The pointer value is independent of the actual data stored in the array, and the pointer value will be the same in both cases no matter whether the fourth or fifth argument to TextOut() gets fully evaluated first. Even if the fourth argument is fully evaluated first, it will evaluate as a pointer to data -- the pointed-to data is what gets changed, not the pointer itself.

To answer your posed question: the actual order of argument evaluation is unspecified. For example, in the statement f(g(), h()), a compliant compiler can execute g() and h() in any order. Further, in the statement f(g(h()), i()), the compiler can execute the three functions g, h, and i in any order with the constraint that h() gets executed before g() -- so it could execute h(), then i(), then g().

It just happens that in this specific case, evaluation order of arguments is wholly irrelevant.

(None of this behavior is dependent on calling convention, which only deals with how the arguments are communicated to the called function. The calling convention does not address in any way the order in which those arguments are evaluated.)

Is the order in which function arguments are resolved guaranteed?

Yes, the order of argument evaluation is guaranteed to be left-to-right.

According to sections 11.2.3 and 11.2.4 of the ES5 spec, function arguments are Argument Lists, and Argument Lists should always be evaluated left to right.

Specifically, the function to call gets evaluated, and then the function's arguments are evaluated from left to right.

What is the order of evaluation for function arguments in Javascript?

All the operators in JavaScript evaluate their operands left-to-right, including the function call operator. First the function to call is evaluated then the actual parameters in left-to-right order.

Section 11.2.3 is the relevant spec section.

11.2.3 Function Calls

...

2 Let func be GetValue(ref).

3 Let argList be the result of evaluating Arguments, producing an internal list of argument values (see 11.2.4).

...

and you can see that the ArgumentList production is left-recursive

11.2.4 Argument lists

...

The production ArgumentList : ArgumentList , AssignmentExpression is evaluated as follows

and ArgumentList is evaluated before AssignmentExpression in the following verbiage..

Under EcmaScript 3 some of the comparison operators (<, <=, >, >=) evaluated right to left since a<=b was defined in terms of !(b<a), but that was widely recognized as a spec error, major interpreters did not implement it that way, and it was fixed in EcmaScript 5.

From the language spec:

11.8.5 The Abstract Relational Comparison Algorithm # Ⓣ

The comparison x < y, where x and y are values, produces true, false, or undefined (which indicates that at least one operand is NaN). In addition to x and y the algorithm takes a Boolean flag named LeftFirst as a parameter. The flag is used to control the order in which operations with potentially visible side-effects are performed upon x and y. It is necessary because ECMAScript specifies left to right evaluation of expressions. The default value of LeftFirst is true and indicates that the x parameter corresponds to an expression that occurs to the left of the y parameter’s corresponding expression. If LeftFirst is false, the reverse is the case and operations must be performed upon y before x. Such a comparison is performed as follows:

Defined argument evaluation order leads to sub-optimal code?

It's partially historical: on processors with few registers, for
example, one traditional (and simple) optimization technique is to
evaluate the subexpression which needs the most registers first. If one
subexpression requires 5 registers, and the other 4, for example, you
can save the results of the one requiring 5 in the register not needed
by the one requiring 4.

This is probably less relevant that usually thought. The compiler can
reorder (even in Java) if the expressions have no side effects, or the
reordering doesn't change the observable behavior of the program.
Modern compilers are able to determing this far better than compilers
twenty or more years ago (when the C++ rule was formulated). And
presumably, when they aren't able to determine this, you're doing enough
in each expression that the extra spill to memory doesn't matter.

At least, that's my gut feeling. I've been told by at least one person
who actually works on optimizers that it would make a significant
difference, so I won't say that I'm sure about it.

EDIT:

Just to add some comments with regards to the Java model. When Java was
being designed, it was designed as an interpreted langauge. Extreme
performance wasn't an issue; the goal was extreme safety, and
reproduceability. Thus, it specifies many things very precisely, so
that any program which compiles will have exactly the same behavior
regardless of the platform. There was supposed to be no undefined
behavior, no implementation defined behavior, and no unspecified
behavior. Regardless of cost (but with the belief that this could be
done at reasonable cost on any of the most widespread machines). One
initial design goals of C (and indirectly C++) was that unnecessary
extra runtime cost should be minimum, that consistency between platforms
wasn't a goal (since at the times, even common platforms varied
greatly), and that safety, while a concern, wasn't primordial. While
the attitudes have evolved some, there is still a goal to be able to
support, efficiently, any machine which might be out there. Without
requiring the newest, most complex compiler technologies. And different
goals naturally lead to different solutions.

Does std::apply provide a guarantee for order of evaluation?

I have a case where it matters that the function is first applied to the first element of the tuple, then the second, then the third, ....

Then you're using the wrong function, because std::apply does something completely unrelated to what you want. For example,

std::apply(f, std::make_tuple(1, 2))

returns

f(1, 2)

rather than trying to call f(1) and f(2) separately.

What are the evaluation order guarantees introduced by C++17?

Some common cases where the evaluation order has so far been unspecified, are specified and valid with C++17. Some undefined behaviour is now instead unspecified.

i = 1;
f(i++, i)

was undefined, but it is now unspecified. Specifically, what is not specified is the order in which each argument to f is evaluated relative to the others. i++ might be evaluated before i, or vice-versa. Indeed, it might evaluate a second call in a different order, despite being under the same compiler.

However, the evaluation of each argument is required to execute completely, with all side-effects, before the execution of any other argument. So you might get f(1, 1) (second argument evaluated first) or f(1, 2) (first argument evaluated first). But you will never get f(2, 2) or anything else of that nature.

std::cout << f() << f() << f();

was unspecified, but it will become compatible with operator precedence so that the first evaluation of f will come first in the stream (examples below).

f(g(), h(), j());

still has unspecified evaluation order of g, h, and j. Note that for getf()(g(),h(),j()), the rules state that getf() will be evaluated before g, h, j.

Also note the following example from the proposal text:

 std::string s = "but I have heard it works even if you don't believe in it"
s.replace(0, 4, "").replace(s.find("even"), 4, "only")
.replace(s.find(" don't"), 6, "");

The example comes from The C++ Programming Language, 4th edition, Stroustrup, and used to be unspecified behaviour, but with C++17 it will work as expected. There were similar issues with resumable functions (.then( . . . )).

As another example, consider the following:

#include <iostream>
#include <string>
#include <vector>
#include <cassert>

struct Speaker{
int i =0;
Speaker(std::vector<std::string> words) :words(words) {}
std::vector<std::string> words;
std::string operator()(){
assert(words.size()>0);
if(i==words.size()) i=0;
// Pre-C++17 version:
auto word = words[i] + (i+1==words.size()?"\n":",");
++i;
return word;
// Still not possible with C++17:
// return words[i++] + (i==words.size()?"\n":",");

}
};

int main() {
auto spk = Speaker{{"All", "Work", "and", "no", "play"}};
std::cout << spk() << spk() << spk() << spk() << spk() ;
}

With C++14 and before we may (and will) get results such as

play
no,and,Work,All,

instead of

All,work,and,no,play

Note that the above is in effect the same as

(((((std::cout << spk()) << spk()) << spk()) << spk()) << spk()) ;

But still, before C++17 there was no guarantee that the first calls would come first into the stream.

References: From the accepted proposal:

Postfix expressions are evaluated from left to right. This includes
functions calls and member selection expressions.

Assignment expressions are evaluated from right to left. This
includes compound assignments.

Operands to shift operators are evaluated from left to right. In
summary, the following expressions are evaluated in the order a, then
b, then c, then d:

  1. a.b
  2. a->b
  3. a->*b
  4. a(b1, b2, b3)
  5. b @= a
  6. a[b]
  7. a << b
  8. a >> b

Furthermore, we suggest the following additional rule: the order of
evaluation of an expression involving an overloaded operator is
determined by the order associated with the corresponding built-in
operator, not the rules for function calls.

Edit note: My original answer misinterpreted a(b1, b2, b3). The order of b1, b2, b3 is still unspecified. (thank you @KABoissonneault, all commenters.)

However, (as @Yakk points out) and this is important: Even when b1, b2, b3 are non-trivial expressions, each of them are completely evaluated and tied to the respective function parameter before the other ones are started to be evaluated. The standard states this like this:

§5.2.2 - Function call 5.2.2.4:

. . .
The postfix-expression is sequenced before each expression in the
expression-list and any default argument. Every value computation and
side effect associated with the initialization of a parameter, and the
initialization itself, is sequenced before every value computation and
side effect associated with the initialization of any subsequent
parameter.

However, one of these new sentences are missing from the GitHub draft:

Every value computation and side effect associated with the
initialization of a parameter, and the initialization itself, is
sequenced before every value computation and side effect associated
with the initialization of any subsequent parameter.

The example is there. It solves a decades-old problems (as explained by Herb Sutter) with exception safety where things like

f(std::unique_ptr<A> a, std::unique_ptr<B> b);

f(get_raw_a(), get_raw_a());

would leak if one of the calls get_raw_a() would throw before the other
raw pointer was tied to its smart pointer parameter.

As pointed out by T.C., the example is flawed since unique_ptr construction from raw pointer is explicit, preventing this from compiling.*

Also note this classical question (tagged C, not C++):

int x=0;
x++ + ++x;

is still undefined.

Function argument evaluation order vs Lambda capture evaluation order

Your std::bind works (bind takes arguments by reference, the closure object's initialization is sequenced before the execution of bind's body, and the move from the promise necessarily happens inside bind).

It's, however, rather pointless since std::thread's constructor can already pass along arbitrary arguments.

std::thread(
[v = std::move(value)](promise<U> pr) {
// ...
},
std::move(pr)
).detach();

Note that std::thread passes the arguments as rvalues, unlike bind.

Evaluation order of function arguments and default arguments

The order of evaluation (i.e. determining the value) of function arguments is not specified. The compiler is free to execute them in any order, and even intermingled if there are no other factors stopping it from doing so.

Evaluation of default arguments happens in the context of the caller, not the callee. So the call to f() is necessary for one argument, and reading the global variable p for the other. Which order this happens in is not specified, so the global could be read before or after the call to f().



Related Topics



Leave a reply



Submit