Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Understanding default member initializers of constructorless structs

172 views
Skip to first unread message

Juha Nieminen

unread,
Feb 8, 2022, 5:32:16 AM2/8/22
to
I have always understood that the default member initializers (introduced
in C++11) merely added that particular initialization value to the
initialization list of a constructor, if it's not explicitly specified
there. In other words, if you do this:

struct A
{
int a = 5, b = 10;

A() {}
A(int v): a(v) {}
};

it results in pretty much the exact equivalent of:

struct A
{
int a, b;

A(): a(5), b(10) {}
A(int v): a(v), b(10) {}
};

The description at https://en.cppreference.com/w/cpp/language/data_members
seems to corroborate this notion. It says there:

Non-static data members may be initialized in one of two ways:
...
2) Through a default member initializer, which is a brace or equals
initializer included in the member declaration and is used if the
member is omitted from the member initializer list of a constructor.

I can't find any other explanation of the meaning and behavior of default
member initializers. (If another behavior is explained somewhere, I can't
find it.)

All the above is find and dandy, except that the meaning of default member
initializers seems to be rather different with constructorless structs
(and classes, I suppose). Consider this:

//---------------------------------------------------------------------------
#include <iostream>

struct S1 { int a = 5, b = 15, c = 25; };
struct S2 { int a, b, c = 35; };
struct S3 { int a = 1, b, c = 3; };

int main()
{
S1 s1 = { 100 };
S2 s2 = { 200, 300 };
S3 s3 = { 400 };

std::cout << "s1 = { " << s1.a << ", " << s1.b << ", " << s1.c << " }\n"
<< "s2 = { " << s2.a << ", " << s2.b << ", " << s2.c << " }\n"
<< "s3 = { " << s3.a << ", " << s3.b << ", " << s3.c << " }\n";
}
//---------------------------------------------------------------------------

It behaves as you would expect it to behave. The members not specified in
the initialization of the variables get the specified default values. So
this is printed:

s1 = { 100, 15, 25 }
s2 = { 200, 300, 35 }
s3 = { 400, 0, 3 }

But how does that work, exactly? The structs have no user-declared
constructors, so where exactly are those default values being "put",
so to speak? How are the objects constructed from the initializater
lists? Does the compiler, like, create an implicitly constructor
for those structs, with three int parameters with the specified
default values? In other words, is for example S1 above equivalent to:

struct S1
{
int a, b, c;

S1(int ia = 5, int ib = 15, int ic = 25):
a(ia), b(ib), c(ic) {}
};

I think it can't be *completely* equivalent to that because gcc,
if you specify -Wextra, will issue a warning from the line:

S3 s3 = { 400 };

warning: missing initializer for member 'S3::b'

s3.b will be initialized to 0 (IIRC this is guaranteed), but gcc
will still give a warning (if enough warning flags are turned on).

Alf P. Steinbach

unread,
Feb 8, 2022, 5:56:19 AM2/8/22
to
I believe but you'll have to check this, that there is a compiler
generated constructor.

Note: there was some change regarding what's regarded as an aggregate
class, an extension I think in C++17, sort of just to baffle people and
screw up old code.


> How are the objects constructed from the initializater
> lists? Does the compiler, like, create an implicitly constructor
> for those structs, with three int parameters with the specified
> default values? In other words, is for example S1 above equivalent to:
>
> struct S1
> {
> int a, b, c;
>
> S1(int ia = 5, int ib = 15, int ic = 25):
> a(ia), b(ib), c(ic) {}
> };
>
> I think it can't be *completely* equivalent to that because gcc,
> if you specify -Wextra, will issue a warning from the line:
>
> S3 s3 = { 400 };
>
> warning: missing initializer for member 'S3::b'
>
> s3.b will be initialized to 0 (IIRC this is guaranteed), but gcc
> will still give a warning (if enough warning flags are turned on).

It looks like a warning without name, so can't be turned off?


- Alf

Juha Nieminen

unread,
Feb 8, 2022, 7:38:40 AM2/8/22
to
Alf P. Steinbach <alf.p.s...@gmail.com> wrote:
>> warning: missing initializer for member 'S3::b'
>
> It looks like a warning without name, so can't be turned off?

No, I just copied the relevant part of the warning for brevity. The full
line is:

test.cc:12:19: warning: missing initializer for member 'S3::b' [-Wmissing-field-initializers]
12 | S3 s3 = { 400 };
| ^

Andrey Tarasevich

unread,
Feb 8, 2022, 10:30:36 AM2/8/22
to
On 2/8/2022 2:31 AM, Juha Nieminen wrote:
> I have always understood that the default member initializers (introduced
> in C++11) merely added that particular initialization value to the
> initialization list of a constructor, if it's not explicitly specified
> there.

Yes, that was the case in C++11. And that was (and is) the initial
driving idea behind default initializers.

However, in C++11 the accompanying side-effect of this was that
specifying default initializers for class members immediately killed
"aggregateness" of the class. In C++11 a class with default initializers
would support `{}` initializer, but reject non-empty `{ ... }`
initializers (unless an appropriate user-declared constructor is present).

In C++11:

struct S
{
int a;
int x = 42;
};

int main()
{
S s1 = {}; // OK
S s2 = { 1 }; // ERROR: no such constructor
S s3 = { 2, 3 }; // ERROR: no such constructor
}


> All the above is find and dandy, except that the meaning of default member
> initializers seems to be rather different with constructorless structs
> (and classes, I suppose). Consider this:

The above "aggregate defying" behavior of default initializers was
reconsidered in C++14. Starting from C++14 a class with default
initializers does not lose its aggregate status and continues to support
aggregate initialization. And in C++14 aggregate initialization was also
made aware of default initializers.

> But how does that work, exactly? The structs have no user-declared
> constructors, so where exactly are those default values being "put",
> so to speak? How are the objects constructed from the initializater
> lists?

Aggregate initialization in C++ never used any "constructors" at the top
level. It is a separate built-in initialization mechanism with its own
custom specification. So, in C++14 this specification was simply
modified to take into account default initializers:

8.5.1 Aggregates
7 If there are fewer initializer-clauses in the list than there are
members in the aggregate, then each member not explicitly initialized
shall be initialized from its brace-or-equal-initializer or, if there is
no brace-or-equalinitializer, from an empty initializer list (8.5.4).

--
Best regards,
Andrey Tarasevich

red floyd

unread,
Feb 8, 2022, 1:57:09 PM2/8/22
to
I think this is correct. I believe it's the C-style aggregate
initialization (inherited from C90).

But because you don't initialize all members of s3, it uses the default
initializers for the second and third members of s3. It's also
providing a (non-mandatory) diagnostic letting you know what it's doing,
but that it may be a mistake.

Juha Nieminen

unread,
Feb 9, 2022, 2:27:32 AM2/9/22
to
Andrey Tarasevich <andreyta...@hotmail.com> wrote:
> Aggregate initialization in C++ never used any "constructors" at the top
> level. It is a separate built-in initialization mechanism with its own
> custom specification. So, in C++14 this specification was simply
> modified to take into account default initializers:
>
> 8.5.1 Aggregates
> 7 If there are fewer initializer-clauses in the list than there are
> members in the aggregate, then each member not explicitly initialized
> shall be initialized from its brace-or-equal-initializer or, if there is
> no brace-or-equalinitializer, from an empty initializer list (8.5.4).

I see. It makes sense. (Although I wonder how they didn't think of that
when they were standardizing C++11.)

Also, I think someone should update the cppreferece.com page to include
this information.
0 new messages