I have no idea how C++ defines this part of its standard but from experience it's likely that it's different in some more or less subtle way which might explain why this is okay. But in the realm of C, without -ffast-math, arithmetic operations on floats can be implemented in any way you can imagine (including having them output to a display in a room full of people with abaci and then interpreting the results of a hand-written sheet returned from said room of people) as long as the observable behaviour is as expected of the semantics.
If this transformation as you describe changes the observable behaviour had it not been applied, then that's just a compiler bug.
This usually means that an operation such as:
double a = x / n;
double b = y / n;
double c = z / n;
printf("%f, %f, %f\n", a, b, c);
Cannot be implemented by a compiler as:
double tmp = 1 / n;
double a = x * tmp;
double b = y * tmp;
double c = z * tmp;
printf("%f, %f, %f\n", a, b, c);
Unless in both cases the same exact value is guaranteed to be printed for all a, b, c, and n.
This is why people enable -ffast-math.