What made i = i++ + 1; legal in C++17?
In C++11 the act of "assignment", i.e. the side-effect of modifying the LHS, is sequenced after the value computation of the right operand. Note that this is a relatively "weak" guarantee: it produces sequencing only with relation to value computation of the RHS. It says nothing about the side-effects that might be present in the RHS, since occurrence of side-effects is not part of value computation. The requirements of C++11 establish no relative sequencing between the act of assignment and any side-effects of the RHS. This is what creates the potential for UB.
The only hope in this case is any additional guarantees made by specific operators used in RHS. If the RHS used a prefix ++
, sequencing properties specific to the prefix form of ++
would have saved the day in this example. But postfix ++
is a different story: it does not make such guarantees. In C++11 the side-effects of =
and postfix ++
end up unsequenced with relation to each other in this example. And that is UB.
In C++17 an extra sentence is added to the specification of assignment operator:
The right operand is sequenced before the left operand.
In combination with the above it makes for a very strong guarantee. It sequences everything that happens in the RHS (including any side-effects) before everything that happens in the LHS. Since the actual assignment is sequenced after LHS (and RHS), that extra sequencing completely isolates the act of assignment from any side-effects present in RHS. This stronger sequencing is what eliminates the above UB.
(Updated to take into account @John Bollinger's comments.)
What is C17 and what changes have been made to the language?
According to GCC reference, C17
is actually a bug-fix version of the C11
standard with DR resolutions integrated.
C17, a bug-fix version of the C11 standard with DR [Defect Report] resolutions
integrated, will soon go to ballot. This patch adds corresponding
options-std=c17
,-std=gnu17
(new default version, replacing
-std=gnu11
as the default),-std=iso9899:2017
. As a bug-fix version
of the standard, there is no need forflag_isoc17
or any options for
compatibility warnings; however, there is a new__STDC_VERSION__
value, so new cpplib languagesCLK_GNUC17
andCLK_STDC17
are added to
support using that new value with the new options. (If the standard
ends up being published in 2018 and being known asC18
, option aliases
can be added. Note however that-std=iso9899:199409
corresponds to a
__STDC_VERSION__
value rather than a publication date.)(There are a couple of DR resolutions needing implementing in GCC, but
that's independent of the new options.)
So, there are no new features included in C17.
The Cppreference (History of C) says:
Future development
C17 Next minor C language standard revision, will include all accepted C11 defect reports, but no new features.
UPDATE:
- 2018: C17 (ISO/IEC 9899:2018) (ISO Store) (Final draft) Includes the deprecation of
ATOMIC_VAR_INIT
and the fixes to the
following defect reports:
[DR 400], [DR 401], [DR 402], [DR 403],
[DR 404], [DR 405], [DR 406], [DR 407],
[DR 410], [DR 412], [DR 414], [DR 415],
[DR 416], [DR 417], [DR 419], [DR 423],
[DR 426], [DR 428], [DR 429], [DR 430],
[DR 431], [DR 433], [DR 434], [DR 436],
[DR 437], [DR 438], [DR 439], [DR 441],
[DR 444], [DR 445], [DR 447], [DR 448],
[DR 450], [DR 452], [DR 453], [DR 457],
[DR 458], [DR 459], [DR 460], [DR 462],
[DR 464], [DR 465], [DR 468], [DR 470],
[DR 471], [DR 472], [DR 473], [DR 475],
[DR 477], [DR 480], [DR 481], [DR 485],
[DR 487], [DR 491]
C++17 sequencing: post-increment on left side of assignment
And the question: is the warning erroneous?
It depends.
Technically, the code in question is well-defined. The right-hand side is sequenced before the left-hand side in C++17, whereas before it was indeterminately sequenced. And gcc compiles the code correctly, v[0] == 1
after that assignment.
However, it is also terrible code that should not be written, so while the specific wording of the warning is erroneous, the actual spirit of the warning seems fine to me. At least, I'm not about to file a bug report about it and it doesn't seem like the kind of thing that's worth developer time to fix. YMMV.
Difference between C++14 and C++17 using: `*p++ = *p`
Reading from and writing to the variable (via the post-increment) used to have undefined behaviour, because the =
did not introduce a sequence point. You could have received either behaviour (or none, or explosions) in C++14.
Now, there is a sequencing order defined for this case and your C++17 results are reliable.
Although it's still bad, unclear code that should not be written!
Shift operands sequenced in C++17
Standard is clear about the order of evaluation of the operands of the shift operator.
n4659 - §8.8 (p4):
The expression
E1
is sequenced before the expressionE2
.
There is no undefined behavior in the expression i++ << i
, it is well defined. It is a bug in Clang and GCC both.
Why did the range based 'for' loop specification change in C++17?
Using
auto __begin = begin_expr, __end = end_expr;
requires both begin_expr
and end_expr
to return the same type. This means you cannot have a sentinel iterator type that is different from the beginning type. Using
auto __begin = begin_expr ;
auto __end = end_expr ;
fixes that issue while proving full backwards compatibility with C++14.
Chained compound assignments with C++17 sequencing are still undefined behaviour?
In order to follow better what is actually performed, let's try to mimic the same with our own type and add some printouts:
class Number {
int num = 0;
public:
Number(int n): num(n) {}
Number operator+=(int i) {
std::cout << "+=(int) for *this = " << num
<< " and int = " << i << std::endl;
num += i;
return *this;
}
Number& operator+=(Number n) {
std::cout << "+=(Number) for *this = " << num
<< " and Number = " << n << std::endl;
num += n.num;
return *this;
}
operator int() const {
return num;
}
};
Then when we run:
Number a {5};
(a += 1) += a;
std::cout << "result: " << a << std::endl;
We get different results with gcc and clang (and without any warning!).
gcc:
+=(int) for *this = 5 and int = 1
+=(Number) for *this = 6 and Number = 6
result: 12
clang:
+=(int) for *this = 5 and int = 1
+=(Number) for *this = 6 and Number = 5
result: 11
Which is the same result as for ints in the question. Even though it is not the same exact story: built-in assignment has its own sequencing rules, as opposed to overloaded operator which is a function call, still the similarity is interesting.
It seems that while gcc keeps the right side as a reference and turns it to a value on the call to +=, clang on the other hand turns the right side to a value first.
The next step would be to add a copy constructor to our Number class, to follow exactly when the reference is turned into a value. Doing that results with calling the copy constructor as the first operation, both by clang and gcc, and the result is the same for both: 11.
It seems that gcc delays the reference to value conversion (both in the built-in assignment as well as with user defined type without a user defined copy constructor). Is it coherent with C++17 defined sequencing? To me it seems as a gcc bug, at least for the built-in assignment as in the question, as it sounds that the conversion from reference to value is part of the "value computation" that shall be sequenced before the assignment.
As for a strange behavior of clang reported in previous version of the original post - returning different results in assert and when printing:
constexpr int foo() {
int res = 0;
(res = 5) |= (res *= 2);
return res;
}
int main() {
std::cout << foo() << std::endl; // prints 5
assert(foo() == 5); // fails in clang 11.0 - constexpr foo() is 10
// fixed in clang 11.x - correct value is 5
}
This relates to a bug in clang. The failure of the assert is wrong and is due to wrong evaluation order of this expression in clang, during constant evaluation in compile time. The value should be 5. This bug is already fixed in clang trunk.
Order of evaluation with function pointers in C++17
The C++17 rule is, from [expr.call]/8:
The postfix-expression is sequenced before each expression in the expression-list and any default argument. The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter.
In (T(f))((f=b,0));
, (T(f))
is sequenced before the initialization of the parameter from (f=b, 0)
. All of this is well-defined and the program should print "a". That is, it should behave just like:
auto __tmp = T(f);
__tmp((f=b, 0));
The same is true even if we change your program such that this were valid:
T{f}(f=b, 0); // two parameters now, instead of one
The f=b
and 0
expressions are indeterminately sequenced with each other, but T{f}
is still sequenced before both, so this would still invoke a
.
Filed 91974.
Is difference between two pointers legal c++17 constant expression?
The question is moot. Pointer arithmetics is only defined on the pointers belonging to the same array, which is certainly not the case there. So, the code above is not legal C++, and in fact, fails to compile with compilers available to me.
Related Topics
C/C++ Include Header File Order
How to Compile For Os X in Linux or Windows
Dividing Two Integers to Produce a Float Result
Why Are Two Different Concepts Both Called "Heap"
Why Are String Literals L-Value While All Other Literals Are R-Value
C++ Template Partial Specialization Member Function
How to Properly Use Widechartomultibyte
Convert a Vector≪Int≫ to a String
Comparing Iterators from Different Containers
Create Wcf Service For Unmanaged C++ Clients
What Makes More Sense - Char* String or Char *String
How to Display a Dynamically Allocated Array in the Visual Studio Debugger
Difference Between Undefined Behavior and Ill-Formed, No Diagnostic Message Required