C++17 has the additional rule which doesn't allow a() and b() to be interleaved. Before C++17, there was no such rule, as far as I know.
There were rules that applied here, though the wording and some details have grown more exact.
C++03 [intro.execution]/8:
Once the execution of a function begins, no expressions from the calling function are evaluated until execution of the called function has completed. [footnote 8]
[footnote 8]: In other words, function executions do not interleave with each other.
Though you could argue this doesn't actually say anything about other functions called from the calling function in the text, and footnotes are officially not normative.
C++11 changed the wording, largely because it introduced multithreading semantics. C++11 and C++14 [intro.execution]/15, emphasis mine:
When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. [Note: Value computations and side effects associated with different argument expressions are unsequenced. - end note] Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function. [footnote 9]
[footnote 9] In other words, function executions do not interleave with each other.
This wording, I think, leaves no doubt, at least in most cases.
C++17 [intro.execution]/18:
When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. For each function invocation F, for every evaluation A that occurs within F and every evaluation B that does not occur within F but is evaluated on the same thread and as part of the same signal handler (if any), either A is sequenced before B or B is sequenced before A. [footnote 10] [Note: If A and B would not otherwise be sequenced then they are indeterminately sequenced. - end note]
[footnote 10] In other words, function executions do not interleave with each other.
This is a much more general statement about all evaluations in separate functions, not just arguments in a function call. But as far as I know, this more precise wording just clarifies some possibly ambiguous cases, but doesn't really change semantic behavior much.