Jest to związane z tzw. punktem sekwencyjnym. https://en.wikipedia.org/wiki/Sequence_point
Standard języka C i C++ definiują że twórcy kompilatora na danej platformie sami ustalają kolejność wykonywania działań operacji tak aby było one wydajne na danej platformie i danym kompilatorze. Pobieranie i zmiana danych w obrębie 1 punktu sekwencyjnego kończy się niezdefiniowanym zachowaniem.
W Twoim przykładzie kompilator jak widać najpierw ustala zawartość zmiennej a.a a później uruchamia funkcję a.b(). Sprawdziłem w moim systemie że g++ zachowuje się tak jak opisałeś (czyli niespodziewane 0) ale już clang++ nie :-)
Podziel swój kod tak jak to opisałeś i będzie działało na każdej platformie.
Tu masz jeszcze jedno takie zachowanie jako przykład. Rozważ co się będzie działo jeśli przetwarzanie będzie od lewej do prawej a co jak będzie odwrotnie.
int x = 12;
x = x++ + ++x;
std::cout << x << '\n';
Jaki twoim zdaniem powinien być x? Takie konstrukcje są po prostu niedopuszczalne :-)