11

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
6
  • It seems to work/compile in msvc 19.3 with C++14. Commented Jun 22, 2022 at 9:02
  • Did you see the gcc-warning tag in my question ? Commented Jun 22, 2022 at 9:04
  • 1
    different case but related stackoverflow.com/q/26947704/4117728 Commented Jun 22, 2022 at 9:15
  • @463035818_is_not_a_number Note that B is an aggregate, which is not the case in the linked post. Commented Jun 22, 2022 at 9:18
  • 1
    @DanielLangr yes its a rather different case, in the other question a change of constructor of std::unordered_map comes into play, which is not relevant here. Though large parts of the accepted answers do apply Commented Jun 22, 2022 at 9:20

1 Answer 1

11

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

Now we move onto dcl.init.list#3.5:

Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.

This means that the object will be value initialized.

Now to value initialize:

To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type ([class]), then
    • if T has either no default constructor ([class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;

So we move onto default initialize:

If T is a (possibly cv-qualified) class type ([class]), constructors are considered.The applicable constructors are enumerated ([over.match.ctor]), and the best one for the initializer () is chosen through overload resolution ([over.match]). The constructor thus selected is called, with an empty argument list, to initialize the object.

Finally from over.match.ctor:

When objects of class type are direct-initialized, copy-initialized from an expression of the same or a derived class type ([dcl.init]), or default-initialized, overload resolution selects the constructor. For direct-initialization or default-initialization that is not in the context of copy-initialization, the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization (including default initialization in the context of copy-initialization), the candidate functions are all the converting constructors ([class.conv.ctor]) of that class. The argument list is the expression-list or assignment-expression of the initializer.

This means that only the converting ctor are candidates. And since A::A(int) is explicit, it is not a converting ctor and thus there the set of candidates is empty and the program(A a ={};) is ill-formed.


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


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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.