Quoting C++11:
5.2.3 Explicit type conversion (functional notation) [expr.type.conv]
2 The expression T()
, where T
is a simple-type-specifier or typename-specifier for a non-array complete object type or the (possibly cv-qualified) void
type, creates a prvalue of the specified type,which is value-initialized (8.5; no initialization is done for the void()
case). [...]
8.5 Initializers [dcl.init]
7 To value-initialize an object of type T
means:
- ...
- if
T
is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T
's implicitly-declared default constructor is non-trivial, that constructor is called.
- ...
So in C++11, S().a
should be zero: the object is zero-initialized before the constructor gets called, and the constructor never changes the value of a
to anything else.
Prior to C++11, value initialization had a different description. Quoting N1577 (roughly C++03):
To value-initialize an object of type T means:
- ...
- if
T
is a non-union class type without a user-declared constructor, then every non-static data member and base-class component of T
is value-initialized;
- ...
- otherwise, the object is zero-initialized
Here, value initialization of S
did not call any constructor, but caused value initialization of its a
and b
members. Value initialization of that a
member, then, caused zero initialization of that specific member. In C++03, the result was also guaranteed to be zero.
Even earlier than that, going to the very first standard, C++98:
The expression T()
, where T
is a simple-type-specifier (7.1.5.2) for a non-array complete object type or the (possibly cv-qualified) void
type, creates an rvalue of the specified type, whose value is determined by default-initialization (8.5; no initialization is done for the void()
case).
To default-initialize an object of type T
means:
- if
T
is a non-POD class type (clause 9), the default constructor for T
is called (and the initialization is ill-formed if T
has no accessible default constructor);
- ...
- otherwise, the storage for the object is zero-initialized.
So based on that very first standard, VC++ is correct: when you add a std::string
member, S
becomes a non-POD type, and non-POD types don't get zero initialization, they just have their constructor called. The implicitly generated default constructor for S
does not initialise the a
member.
So all compilers can be said to be correct, just following different versions of the standard.
As reported by @Columbo in the comments, later versions of VC++ do cause the a
member to be initialized, in accordance with more recent versions of the C++ standard.