Is there a way to force a specific evaluation order of function arguments? [duplicate]
Asked Answered
F

2

22

I understand that when I call a function such as

a(b(),c());

then the behavior of this may be undefined in <= C++14, and unspecified in >= C++17, in the sense that it is up to the compiler to determine whether to evaluate b or c first.

I would like to know the best way to force an evaluation order. I will be compiling as C++14.

The thing that immediately comes to mind is something like this:

#include <iostream>

int count = 5;
auto increment(){
    return count++;
}

template <typename A, typename B>
auto diff(A && a, B && b){
   return a - b;
}

int main() {
    auto && a = increment();
    auto && b = increment();
    auto c = diff(a,b);
}

Am I in undefined behavior land? Or is this how one is "supposed" to force evaluation order?

Fastigium answered 24/5, 2019 at 9:40 Comment(10)
why do you think there could be ub?Dissected
I dunno. I hear many people complain about the ub with C++14 in the first example. But the approach I outline seems obvious to me, so I feel like there is something that I am missing.Fastigium
Like some gottcha that I should be aware of, specifcally when saving the auto && a = bar(); and then passing that to another function.Fastigium
You are right. I guess if I had used a diff instead of a sum, then it would be ub. That is what I meant.Fastigium
@Peter even if a global variable is used (read and/or written) by the two functions, the behavior would not be undefined, but just unspecified.Heaves
@Peter Expressions can be evaluated in an interleaved manner up to C++14, but even then each function invocation in an argument must be executed as a whole. It has never been the case that functions could have been executed interleaved or in parallel. See rule 11 in the Rules section (and rule 4 in the sequence point rules further down).Heaves
@bremen_matt: diff doesn't make a difference (pun intended), it is still just unspecified, not UB. There is no difference between standard versions in this regard, this was always unspecified.Armenian
Is there anything in the standard that would stop a C++14 compiler from choosing randomly at runtime whether to evaluate b or c first in the expression a(b(),c())? That seems to be undefined behavior.Fastigium
I may just be confusing unspecified and undefinedFastigium
@bremen_matt: Undefined means anything can happen (the standard doesn't specify what should happen). Unspecified means b() and then c(), or c() and then b(). One of them will happen. It is not specified, which.Armenian
B
28

The semi-colon that separates statements imposes a "happens before" relation. auto && a = increment() must be evaluated first. It is guaranteed. The returned temporary will be bound to the reference a (and its lifetime extended) before the second call to increment.

There is no UB. This is the way to force an evaluation order.

The only gotcha here is if increment returned a reference itself, then you'd need to worry about lifetime issues. But if there was no lifetime issues, say if it returned a reference to count, there still would not be UB from the imposed evaluation of a and then b.

Below answered 24/5, 2019 at 9:43 Comment(0)
C
17

Here's another way to force the evaluation order, using a std::initializer_list, which has a guaranteed left-to-right order of evaluation:

#include <numeric> // for accumulate
#include <initializer_list>

template <class T>
auto diff(std::initializer_list<T> args)
{
   return std::accumulate(args.begin(), args.end(), T(0), std::minus<>{});
}

const auto result = diff({increment(), increment()});

This restricts you to objects of the same type, and you need to type additional braces.

Camilla answered 24/5, 2019 at 9:45 Comment(4)
Not sure but I think you can get this to work with different types via tuples: std::apply(func, std::tuple<funcsargs>({__VA_ARGS__}));Whangee
apply is C++17 I thinkFastigium
@Fastigium Thanks for the edit for consistency with the question. Minor nitpick: std::accumulate does std::plus<> by default, so we need to pass a std::minus<> instance to do the actual subtraction.Camilla
Oops. Slipped through the cracks. I changed the example slightly so that it is easier for others to understand why order of execution is important. In the previous example, it actually didn't matter.Fastigium

© 2022 - 2024 — McMap. All rights reserved.