In Bjarne Stroustrup's The C++ Programming Language 4th edition section 36.3.6 STL-like Operations the following code is used as an example of chaining:
void f2()
{
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, "" );
assert( s == "I have heard it works only if you believe in it" ) ;
}
The assert fails in gcc (see it live) and Visual Studio (see it live), but it does not fail when using Clang (see it live).
Why am I getting different results? Are any of these compilers incorrectly evaluating the chaining expression or does this code exhibit some form of unspecified or undefined behavior?
The code exhibits unspecified behavior due to unspecified order of evaluation of sub-expressions although it does not invoke undefined behavior since all side effects are done within functions which introduces a sequencing relationship between the side effects in this case.
This example is mentioned in the proposal N4228: Refining Expression Evaluation Order for Idiomatic C++ which says the following about the code in the question:
Details
It may be obvious to many that arguments to functions have an unspecified order of evaluation but it is probably not as obvious how this behavior interacts with chained functions calls. It was not obvious to me when I first analyzed this case and apparently not to all the expert reviewers either.
At first glance it may appear that since each
replacehas to be evaluated from left to right that the corresponding function argument groups must be evaluated as groups from left to right as well.This is incorrect, function arguments have an unspecified order of evaluation, although chaining function calls does introduce a left to right evaluation order for each function call, the arguments of each function call are only sequenced before with respect to the member function call they are part of. In particular this impacts the following calls:
and:
which are indeterminately sequenced with respect to:
the two
findcalls could be evaluated before or after thereplace, which matters since it has a side effect onsin a way that would alter the result offind, it changes the length ofs. So depending on when thatreplaceis evaluated relative to the twofindcalls the result will differ.If we look at the chaining expression and examine the evaluation order of some of the sub-expressions:
and:
Note, we are ignoring the fact that
4and7can be further broken down into more sub-expressions. So:Ais sequenced beforeBwhich is sequenced beforeCwhich is sequenced beforeD1to9are indeterminately sequenced with respect to other sub-expressions with some of the exceptions listed below1to3are sequenced beforeB4to6are sequenced beforeC7to9are sequenced beforeDThe key to this issue is that:
4to9are indeterminately sequenced with respect toBThe potential order of evaluation choice for
4and7with respect toBexplains the difference in results betweenclangandgccwhen evaluatingf2(). In my testsclangevaluatesBbefore evaluating4and7whilegccevaluates it after. We can use the following test program to demonstrate what is happening in each case:Result for
gcc(see it live)Result for
clang(see it live):Result for
Visual Studio(see it live):Details from the standard
We know that unless specified the evaluations of sub-expressions are unsequenced, this is from the draft C++11 standard section
1.9Program execution which says:and we know that a function call introduces a sequenced before relationship of the function calls postfix expression and arguments with respect to the function body, from section
1.9:We also know that class member access and therefore chaining will evaluate from left to right, from section
5.2.5Class member access which says:Note, in the case where the id-expression ends up being a non-static member function it does not specify the order of evaluation of the expression-list within the
()since that is a separate sub-expression. The relevant grammar from5.2Postfix expressions:C++17 changes
The proposal p0145r3: Refining Expression Evaluation Order for Idiomatic C++ made several changes. Including changes that give the code well specified behavior by strengthening the order of evaluation rules for postfix-expressions and their expression-list.
[expr.call]p5 says: