What you see, is a result of allowed by standard optimization, when compiler avoids creation of temporary. Compiler is allowed to replace construction and assignment with simple construction even in presence of side effects (like IO in your example).
But fact if program is ill-formed or not should not depend on situation, when compiler makes this optimization or not. That's why
Foo f = Foo(3);
requires copy constructor. And
Foo f(3);
does not. Though it will probably lead to same binary code.
Quote from 12.8.15
When certain criteria are met, an
implementation is allowed to omit the
copy construction of a class object,
even if the copy constructor and/or
destructor for the object have side
effects. In such cases, the
implementation treats the source and
target of the omitted copy operation
as simply two different ways of
referring to the same object, and the
destruction of that object occurs at
the later of the times when the two
objects would have been destroyed
without the optimization.111) This
elision of copy operations is
permitted in the following
circumstances (which may be combined
to eliminate multiple copies):
— in a
return statement in a function with a
class return type, when the expression
is the name of a non-volatile
automatic object with the same
cv-unqualified type as the function
return type, the copy operation can be
omitted by constructing the automatic
object directly into the function’s
return value
— when a temporary class
object that has not been bound to a
reference (12.2) would be copied to a
class object with the same
cv-unqualified type, the copy
operation can be omitted by
constructing the temporary object
directly into the target of the
omitted copy
See also "Return value optimization".