Let's talk about subexpressions and their sequencing. If E1
is sequenced before E2
, that means E1
must be fully evaluated before E2
is. If E1
is unsequenced with E2
, that means E1
and E2
may be evaluated in any order.
For f(A()) << B()
, which is in your case the same as f(A()).operator<<(B())
, we know that:
A()
is sequenced beforef(...)
,f(...)
is sequenced beforeoperator<<
andB()
is sequenced beforeoperator<<
This also tells us that:
A()
is sequenced beforeoperator<<
A()
is unsequenced withB()
f(...)
is unsequenced withB()
If we assume RVO, so as not to complicate things, the possible order a compiler could evaluate the subexpressions in are:
A()
->f(...)
->B()
, yielding~B()
->~C()
->~A()
A()
->B()
->f(...)
, yielding~C()
->~B()
->~A()
B()
->A()
->f(...)
, yielding~C()
->~A()
->~B()
The latter is the order observed in the OP. Note that the order of destruction is always the reverse order of construction.