Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.c++ > #82970 > unrolled thread

Understanding default member initializers of constructorless structs

Started byJuha Nieminen <nospam@thanks.invalid>
First post2022-02-08 10:31 +0000
Last post2022-02-09 07:26 +0000
Articles 6 — 4 participants

Back to article view | Back to comp.lang.c++


Contents

  Understanding default member initializers of constructorless structs Juha Nieminen <nospam@thanks.invalid> - 2022-02-08 10:31 +0000
    Re: Understanding default member initializers of constructorless structs "Alf P. Steinbach" <alf.p.steinbach@gmail.com> - 2022-02-08 11:55 +0100
      Re: Understanding default member initializers of constructorless structs Juha Nieminen <nospam@thanks.invalid> - 2022-02-08 12:37 +0000
        Re: Understanding default member initializers of constructorless structs red floyd <no.spam.here@its.invalid> - 2022-02-08 10:56 -0800
    Re: Understanding default member initializers of constructorless structs Andrey Tarasevich <andreytarasevich@hotmail.com> - 2022-02-08 07:29 -0800
      Re: Understanding default member initializers of constructorless structs Juha Nieminen <nospam@thanks.invalid> - 2022-02-09 07:26 +0000

#82970 — Understanding default member initializers of constructorless structs

FromJuha Nieminen <nospam@thanks.invalid>
Date2022-02-08 10:31 +0000
SubjectUnderstanding default member initializers of constructorless structs
Message-ID<sttgpr$1cud$1@gioia.aioe.org>
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).

[toc] | [next] | [standalone]


#82971

From"Alf P. Steinbach" <alf.p.steinbach@gmail.com>
Date2022-02-08 11:55 +0100
Message-ID<stti72$hp4$1@dont-email.me>
In reply to#82970
On 8 Feb 2022 11:31, 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. 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?

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

[toc] | [prev] | [next] | [standalone]


#82972

FromJuha Nieminen <nospam@thanks.invalid>
Date2022-02-08 12:37 +0000
Message-ID<stto70$106i$1@gioia.aioe.org>
In reply to#82971
Alf P. Steinbach <alf.p.steinbach@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 };
      |                   ^

[toc] | [prev] | [next] | [standalone]


#82974

Fromred floyd <no.spam.here@its.invalid>
Date2022-02-08 10:56 -0800
Message-ID<stuecp$nib$1@redfloyd.dont-email.me>
In reply to#82972
On 2/8/2022 4:37 AM, Juha Nieminen wrote:
> Alf P. Steinbach <alf.p.steinbach@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 };
>        |                   ^

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.

[toc] | [prev] | [next] | [standalone]


#82973

FromAndrey Tarasevich <andreytarasevich@hotmail.com>
Date2022-02-08 07:29 -0800
Message-ID<stu29c$v3$1@dont-email.me>
In reply to#82970
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

[toc] | [prev] | [next] | [standalone]


#82975

FromJuha Nieminen <nospam@thanks.invalid>
Date2022-02-09 07:26 +0000
Message-ID<stvqbi$1ulv$1@gioia.aioe.org>
In reply to#82973
Andrey Tarasevich <andreytarasevich@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.

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.c++


csiph-web