Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explicit default constructor

Tags:

This code compiles fine with GCC 5.X, MSVC, but GCC 6.X gives error:

"converting to 'a' from initializer list would use explicit constructor 'a::a()'", clang "chosen constructor is explicit in copy-initialization".

Removing explicit or changing to a c{} fixes the problem, but I`m curious why it works this way.

class a
{
public:
    explicit a () {}
};
struct b
{
    a c;
};

int main() {
    b d{};
}
like image 566
geniculata Avatar asked Feb 08 '18 04:02

geniculata


People also ask

What is a explicit constructor?

The explicit function specifier controls unwanted implicit type conversions. It can only be used in declarations of constructors within a class declaration. For example, except for the default constructor, the constructors in the following class are conversion constructors.

Can we call a default constructor explicitly?

Yes, it is possible to call special member functions explicitly by the programmer.

How do you initialize an explicit constructor?

There are two ways to initialize a class object: Using a parenthesized expression list. The compiler calls the constructor of the class using this list as the constructor's argument list. Using a single initialization value and the = operator.

What does explicit constructor mean in C++?

The explicit keyword in C++ is used to mark constructors to not implicitly convert types. For example, if you have a class Foo − class Foo { public: Foo(int n); // allocates n bytes to the Foo object Foo(const char *p); // initialize object with char *p };


1 Answers

b is an aggregate. When you initialize it using an initializer list, the elements in the list will initialize the first n members of the aggregate, where n is the number of elements in the list. The remaining elements of the aggregate are copy-list-initialized.

So in your example, c will be copy-list-initialized, but that is ill-formed if the chosen constructor is explicit, hence the error.

The relevant standard quotes are

[dcl.init.aggr]/3

When an aggregate is initialized by an initializer list as specified in [dcl.init.list], the elements of the initializer list are taken as initializers for the elements of the aggregate. The explicitly initialized elements of the aggregate are determined as follows:
...
— 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.
— Otherwise, the initializer list must be {}, and there are no explicitly initialized elements.

[dcl.init.aggr]/5

For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows:
...
— Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).

The effect of copy initializing c from an empty initializer list is described in

[dcl.init.list]/3

List-initialization of an object or reference of type T is defined as follows:
...
— Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.

[dcl.init]/8

To value-initialize an object of type T means:
...
— if T is a (possibly cv-qualified) class type with either no default constructor ([class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;

[dcl.init]/7

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

[over.match.ctor]

... For copy-initialization, the candidate functions are all the converting constructors of that class.

[class.conv.ctor]/1

A constructor declared without the function-specifier explicit specifies a conversion from the types of its parameters (if any) to the type of its class. Such a constructor is called a converting constructor.

In the example above, a has no converting constructors, so overload resolution fails. The (non-normative) example in [class.conv.ctor]/2 even contains a very similar case

  struct Z {
    explicit Z();
    explicit Z(int);
    explicit Z(int, int);
  };

  Z c = {};                       // error: copy-list-initialization

You can avoid the error by providing a default member initializer for c

struct b
{
    a c{};  // direct-list-initialization, explicit ctor is OK
};
like image 69
Praetorian Avatar answered Oct 29 '22 08:10

Praetorian