fenbf / cppstories-discussions

4 stars 1 forks source link

2021/evaluation-order-cpp17/ #50

Open utterances-bot opened 3 years ago

utterances-bot commented 3 years ago

Stricter Expression Evaluation Order in C++17 - C++ Stories

C++ has many dark corners and many caveats that can cause you to scratch your head in confusion. One of the issues we had until C++17 was the evaluation order of expressions. In this blog post, I’ll show you the new rules that we got in C++17 that made this complicated term much simpler and practical.

https://www.cppstories.com/2021/evaluation-order-cpp17/

fenbf commented 3 years ago

let's start a discussion for this topic

YehezkelShB commented 3 years ago

Nice blog post on an important, but usually ignored, topic!

the compiler won’t be allowed to call otherFunction() before the expression unique_ptr<T>(new T) is fully evaluated

As you explain in the next section, the order of parameter evaluation isn't specified. So the order isn't guaranteed, despite what the reader might understand from the quoted sentence. The compiler may choose to call the function before allocating the memory. It's just that if it's started with the memory allocation it must complete the evaluation of the expression before calling the function (or at least act as-if, as usual).

If the compiler chooses to evaluate a(x) first, then it must evaluate x before processing b, c(y) or y.

I guess it should be the other way around, if the compiler chooses to evaluate x first, then it must evaluate a(x) before processing b, c(y) or y. It's impossible to evaluate a(x) before evaluating x, so if you already said a(x) is evaluated first, it means x was already evaluated, which is why the current phrasing doesn't make sense to me.

expA is evaluated before calling b()

Again, this is given, even in C++98. It can't work otherwise. What you might mean here is that if b itself is an expression (e.g. a function pointer that must be dereferenced to call the function or a temporary function object created in-place), exprA must be evaluated before evaluating (not even calling) b.

Nice example at the end for a still-possible pitfall :)

2kaud commented 3 years ago

For rule 6, a[b] this can also be written b[a] so 'side effects' could be different.

ie c[3] can also be coded as 3[c] According to the rule,, in the first c is evaluated and then 3 but in the second then 3 is evaluated first then c. I'm sure some 'fun' can be had with this construct...

fenbf commented 3 years ago

thanks @YehezkelShB ! I've just improved the wording and the text should be better now.

as for the last case with expA - I think before C++17 the compiler could evaluate expB, expA, expC, so the order wasn't guaranteed.

alec-chicherini commented 3 years ago

Thanks for the interesting article, I didn't even know that order matters in this case.

After a little experimenting with different GCC(x86-64 gcc ...) versions and flags, in CompilerExplorer here's what I got:

1) from gcc 4.1.2 to 4.9.3
with -std=c++11 flag the result is first case. with -std=c++17 error - c++17 not implemented in this version 2) from gcc 5.1 to 6.4 with -std=c++11 flag the result is first case. with -std=c++17 flag the result is first case too!(not expected!?) 3) from gcc 7.1 to 11.2 with -std=c++11 flag the result is second case too! (not expected!?) with -std=c++17 flag the result is second case.

(first case GCC 4.7 -std=c++11 from article, second case GCC 8 -std=c++17 from article)

This is not directly this topic but anyway, i want to clarify why c++11 and c++17 flag usage not not leading to expected behaviour? I always thought that if some new behaviour has been added it only affect on new version? Or GCC creators just decided to implement in new GCC versions this c++17 behaviour that it works even with c++11 flag to avoid ambiguity? Or after proposal with new evaluation order it was added in c++17 like standart and in c++11 like ... patch? P.S. Here is an idea for the new article, "The way of new features in C++. From proposal to implementation." :) For me as a newbie in C ++ it would be interesting to know.

fenbf commented 3 years ago

@ikvasir - thanks for checking. std-c++17 is simply not recognized by some older GCC version, so that's why you might get compiler errors. As for the differences in results, as you can see in C++11 you can get different results depending on the compiler version. So you couldn't rely on it in any case.

I don't have separate articles on proposals, but have a look: The life of an ISO proposal: From "cool idea" to "international standard" : Standard C++, it's from the official ISO website.

YehezkelShB commented 3 years ago

@ikvasir According to cppreference, support of "Stricter expression evaluation order" was added to GCC only in version 7, which explains your #2. Using c++17 flag just says the compiler enables on the currently implemented C++17 features, it doesn't imply that compiler already supports all the new features of this standard version. (Actually, some minor and dark corners of C++17 are still not implemented in gcc, for good reasons, and at least one of them will probably never be.)

BTW, the official term for "patch" for older standards is DR - defect report. Officially, the standard committee applies DRs only on the latest standard (besides adding the fix to the current draft, which will become the next version), but compilers tend to implement the DR for previous standard versions too in most cases, as much as I can tell.

For evaluation order, it isn't a DR, but as Bartłomiej explained, compilers were free to choose the evaluation order till C++17, so to my understanding it makes sense to have the C++17 evaluation order used for all standards instead of keeping different code paths inside the compiler depending on the chosen standard version.

geoyar commented 3 years ago

Now I understand: In VS 2019 the project with ISO C++17 happily build file with file Nm.Cpp class B; (forward def) class A that using class B; (template using B ) class B itself; (template)

But with last changes options it says no definition of class B.

YehezkelShB commented 3 years ago

@geoyar

Now I understand: In VS 2019 the project with ISO C++17 happily build file with file Nm.Cpp class B; (forward def) class A that using class B; (template using B ) class B itself; (template)

But with last changes options it says no definition of class B.

Doesn't sound related to order of expression evaluation. I'd guess it's related to "two-phase name lookup". https://docs.microsoft.com/en-us/cpp/build/reference/zc-twophase

According to /permissive- documentation, this standard-conforming mode is enabled when using /std:c++20 or /std:c++latest flags, which might explain how this behavior change was enabled for you

SimonSchroeder commented 3 years ago

Do I have to write foo(unique_ptr<T>(new T), otherFunction()); explicitly?

Or is it sufficient if foo() is already defined with unique_ptr<T> as the first parameter, to just write foo(new T, otherFunction());? Is the evaluation order still well enough defined to not leak memory?

fenbf commented 3 years ago

@SimonSchroeder maybe it's a contradiction, and I should improve the text... but in fact it's best to rely on make_unique to create a new unique pointer. This is not to protect against evaluation order, but to avoid writing naked new/delete in code (Modern C++ suggestion).

mvorbrodt commented 2 years ago

Good stuff! I'm glad this was addressed in C++17. For everyone else, if you haven't read Bartek's books you should definitely check them out! He writes very well about C++.

BTW, I've blogged in the past about shared_ptr and gave a similar example: https://vorbrodt.blog/2019/10/13/in-depth-look-at-c-shared-pointers/

fenbf commented 2 years ago

thanks @mvorbrodt !