Yes. And it is not uncommon in many fields to blame the tools for
mistakes of the users. Sometimes such blame is fair, of course - but
usually not.
> Yet it is empirically valid that those diverse alternatives
> don't have the same safety, and we evolve things accordingly.
>
> Speaking of returning the cross-posting to comp.lang.c++, that language
> as of C++17 has chosen to define some evaluation orders.
>
Yes. Primarily it is for the case of "cout << a() << b();". This is a
situation where specific ordering really does help the programmer.
Language changes are sometimes a good idea, but there are almost always
trade-offs to consider. Things that one person sees as clearly a good
idea, will sound crazy and be a pain for someone else.
> The evaluation order of function arguments is not yet defined, but
> now there is a sequence point between the argument expressions.
>
> That is obviouslly super important, because now the unspecified behavior
> stays unspecified when there are side effects like:
>
> f(i++, i++);
>
> it doesn't help with the situation
>
> f(g(), h())
>
> because those calls are sequenced. However, it helps in the
> argument space: with just the classic sequencing of function calls not
> being interleaved, this is still undefined:
>
> f(g(i++), h(i++))
>
There is always going to be things that are undefined or unspecified.
There are always going to be ways to write things that make no sense, or
at least no well-defined and consistent sense. A language can aim to
reduce these possibilities, but it comes at a big cost. There are three
disadvantages, as I see them. One is that it tells programmers that
even though a piece of code is unclear to humans, it is valid code - and
then some people write code like "f(i++, i++)" that is hard for others
to comprehend. Another is it reduces the ability of compilers and tools
to help you write good, clear code (compilers can warn on "f(i++, i++)"
when it is not defined behaviour). And it reduces optimisation
possibilities for perfectly good code.
> whereas by my understanding C++17 leaves it unspecified in which order g
> and h will be called, and which one will have receive the original value
> of i. But, I think, we can infer that that function which is called
> first receives the original value of i, and i is reliably incremented
> twice. Baby steps in the right direction, in any case.
>
No - that is the /wrong/ message to take from the C++17 changes. It is
not "baby steps in the right direction" - it is a case of minimal
changes needed to give defined behaviour to common incorrect code that
has been used in practice for years. The key motivation is that a lot
of code has been written that assumes "cout << f() << g();" calls "f()"
first, then "g()".
It is not a step in a direction towards more general changes, because
that would mean making at least some existing correct code less efficient.
> I don't understand the rationale for sequencing A before B in A << B
> (what is so special about shifting); there must be some motivating
> example where you have a side effect in A that B depends on. What I'm
> likely missing that it's probably not the built-in arithmetic << that is
> of concern, but overloads.
>
Correct.
>> But you don't get to pick different rules that you'd prefer to have for
>> the language, and then blame the language when your code doesn't work.
>> It is not the language that is at fault if someone writes code that
>> relies on execution order that is left unspecified by the standards -
>> it's the programmers' fault.
>
> It's not a game of blame, but of reducing the unfortunate situations in
> which someone has a reason to look for something to blame.
>
Fair enough.
> I can't look at a large body of code (that I perhaps didn't write: so no
> responsibility of mine) and easily know whether there is a problem due
> to eval order.
Analysing the correctness of other people's code is never an easy job!
However, I don't think a specified evaluation order would help. If you
see "f(g(), h());" in the code, you are concerned that the programmer
might be assuming that "g()" is evaluated before "h()". But if you know
the programmer was good at his/her job, you will know that he/she would
have used temporaries and run g() and h() before f() if the order
mattered. So you know the code is correct, and the order does not matter.
However, if the order were specified by the language, then you still
know the code is correct but you don't know if the order of the call
matters (and the programmer relied on the evaluation order), or if it
does not matter.
The lack of specification in the language gives you /more/ information,
not less.
And if you can't rely on the original programmer's competence, you can't
rely on anything in the code without more detailed checking anyway.
Sometimes tighter specification for things like evaluation order gives
you benefits, but not always - it can just as well be a disadvantage.