They complain about that for the same reason they may complain about:
if (a = 0) { ... }
It's a common error people often make (to assign to a variable in a conditional statement, rather than comparing with ==
), despite the fact it's perfectly legal to do so.
In your case, it's considered a common-enough problem for people to initialise fewer fields than actually exist, so it lets you know about it.
That's why it's a warning rather than an error. It's telling you you should examine your code and ensure it's doing what you expect. If it is, you can almost certainly silence the warning with, for example, a compiler flag to turn it off, or a more localised #pragma
. Or better yet, use the empty initialiser list {}
rather than {0}
.
Regarding your specific bullet points:
there's no constructor, it's an extern "C" struct
: yes, that's correct. If it had a constructor, the initialisation would follow a different path through the standard to what I've documented below.
it doesn't complain about field1, which would seem to imply that there's some aggregate or direct-initialization going on.. but see #1
: again, correct, but it's not complaining that the fields aren't initialised, just that you explicitly initialised some but not others. That's the possible problem it's bringing to your intention.
both field1 and field2 are definitely initialized
: see previous point.
Based on that (and the analysis below), I would suggest using Foo foo = {}
. A simple Foo foo
will not initialise any of the fields, which is probably not what you want. But Foo foo = {}
will:
- explicitly initialise no fields; then
- implicitly value-initialise the remaining fields (i.e., all fields).
And it's likely to get rid of the warning since you're not explicitly initialising only a subset.
For the language lawyers amongst us, this is covered in C++20 [dcl.init]
, which deals with the declarations with initialisations, such as your case of this question.
Specifically, Foo foo = {0}
initialises the first field explicitly and the others based on certain rules. The equivalent functionality is to treat it as:
Foo foo;
foo.field1 = 0; // explicit init
foo.field2 = 0; // implicit/value init
That's due to the following sections in the standard, first the explicit initialisation of field1
with zero:
[dcl.init.aggr] (3.2)
If the initializer list is an initializer-list, the explicitly initialized elements of the aggregate are the first n
elements of the aggregate, where n
is the number of elements in the initializer list.
The the implicit initialisation of all other fields as if they were initialised to {}
:
[dcl.init.aggr] (5)
For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows:
[dcl.init.aggr] (5.1)
— If the element has a default member initializer, the element is initialized from that initializer.
[dcl.init.aggr] (5.2)
Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list.
Finally, the sections that state what happens when you initialise with {}
. (3.11)
is the first match to your scenario, from a long sequence of checks:
[dcl.init.list] (3.11)
Otherwise, if the initializer list has no elements, the object is value-initialized.
[dcl.init] (8)
To value-initialize an object of type T means:
[dcl.init] (8.1)
SOME IRRELEVANT STUFF
[dcl.init] (8.2)
SOME IRRELEVANT STUFF
[dcl.init] (8.3)
SOME IRRELEVANT STUFF
[dcl.init] (8.4)
Otherwise, the object is zero-initialized.
Hence, zero initialisation of your int
field is happening here even without your explicit request.