9

I am trying to migrate an old C++03 codebase to C++11. But I fail to understand what gcc is warning me about in the following case:

% g++ -std=c++03 t.cxx
% g++ -std=c++11 t.cxx
t.cxx: In function ‘int main()’:
t.cxx:8:21: warning: converting to ‘A’ from initializer list would use explicit constructor ‘A::A(int)’
    8 | int main() { B b = {}; }
      |                     ^
t.cxx:8:21: note: in C++11 and above a default constructor can be explicit
struct A {
  explicit A(int i = 42) {}
};
struct B {
  A a;
};
    
int main() {
  B b = {};
  return 0;
}

All I am trying to do here is a basic zero initialization. It seems to be legal for C++03, but I fail to understand how to express the equivalent in C++11.


For reference, I am using:

% g++ --version
g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
malat
  • 12,152
  • 13
  • 89
  • 158

1 Answers1

7

The given program is ill-formed for the reason(s) explained below.

C++20

B is an aggregate. Since you're not explicitly initializing a, dcl.init.aggr#5 applies:

  1. For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows:

5.2 Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).

This means that a is copy initialized from an empty initializer list. In other word, it is as if we're writing:

A a = {}; // not valid see reason below

Note also that list-initialization in a copy initialization context is called copy-list-initialization which is the case here. And from over.match.list#1.2:

In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.


Essentially, the reason for the failure is that A a = {}; is ill-formed.


C++11

Since B is an aggregate and there are fewer initializer-clauses in the list than there are members in the aggregate, and from aggregate initialization documentation:

The effects of aggregate initialization are:

  • If the number of initializer clauses is less than the number of members and bases (since C++17) or initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default member initializers, if provided in the class definition, and otherwise (since C++14) copy-initialized from empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates).

This again means that a is copy initialized from an empty list {}. That is, it is as if we're writing:

A a = {}; // not valid see reason below

But from over.match.funcs:

In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.

So again we face the same problem i.e., A a = {}; is not valid.


Solution

To solve this, we can pass A{} or A{0} as the initializer inside the list as shown below:

B b = { A{} };  //ok now 
B c = { A{0} }; //also ok

Working demo.


Note

Note that writing A a{}; on the other hand is well-formed as this is a direct-initialization context and so it is direct-list-initialization.

Jason
  • 36,170
  • 5
  • 26
  • 60