I learnt about the explicit keyword in c++ and its uses. Then for practice I wrote the following program that works with MSVC but not with gcc and clang. Demo.
class Testing
{
public:
explicit Testing() = default;
};
int main()
{
Testing t[2] = {}; //works with msvc but not with gcc and clang
}
As you can see, the above works with msvc but not with clang and gcc. I am using C++20 and want to know which compiler is correct for this example according to the C++20 standard.
GCC produces the following error:
<source>:8:21: error: converting to 'Testing' from initializer list would use explicit constructor 'constexpr Testing::Testing()'
8 | Testing t[2] = {}; //works with msvc but not with gcc and clang
| ^
The program is ill-formed and gcc and clang are correct in rejecting the program because Testing t[2]={}; is copy-list-initialization and since t is an array it uses aggregate initialization which in turn result in copy initialization of t's member(s) using the empty initializer list {} which fails as the default ctor is explicit(and so Testing::Testing() cannot be used in copy initialization).
This is explained in detail below.
From list initialization:
3) List-initialization of an object or reference of type T is defined as follows:
3..4) Otherwise, if T is an aggregate, aggregate initialization is performed
This means that since T in our example is Testing[2] which is an array(and so an aggregate), aggregate initialization will be performed.
Next, from aggregate initialization:
5) For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows:
5.4) Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list.
This means that the array elements(which are not explicitly initialized btw) will be copy initialized from an empty initializer list {}. What this in turn means is that it is as if for each element of the array we're writing Testing array_element_nth = {}; which results in value-initialization as per 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.
Next, from value initialization:
To value-initialize an object of type T means:
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;
This means that the array element will be default initialized.
So we move onto default initialization:
To default-initialize an object of type T means:
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.
This means that that the constructors will be enumerated and best of them will be chosen with (). So we move onto 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 the default ctor will be used but since this is in copy initialization context, the explicit ctor cannot be used.
Thus, msvc is wrong in accepting the program.
Here is the msvc bug:
MSVC compiles invalid program involving explicit constructor
Related
In the following program the object A a is directly initialized from braced-init-list {A{}}:
#include <iostream>
struct A {
int v = 0;
A() {}
A(const A &) : v(1) {}
};
int main() {
A a({A{}});
std::cout << a.v;
}
MSVC and GCC print 0 here meaning that copy-elision takes place. And Clang prints 1 executing the copy-constructor.
Online demo: https://gcc.godbolt.org/z/1vqvf148z
Which compiler is right here?
Which compiler is right here?
I think that clang is right in using the copy constructor and printing 1 for the reason(s) explained below.
First note that A a({A{}}); is direct-initialization as can be seen from dcl.init#16.1:
The initialization that occurs:
16.1) for an initializer that is a parenthesized expression-list or a braced-init-list,
16.2) for a new-initializer,
16.3) in a static_cast expression ([expr.static.cast]),
Now, dcl.init#17.6 is applicable here:
17.6) Otherwise, if the destination type is a (possibly cv-qualified) class type:
17.6.1) If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object.
[ Example: T x = T(T(T())); calls the T default constructor to initialize x.
— end example
]
17.6.2) Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered.
The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution ([over.match]).
Then:
17.6.2.1) If overload resolution is successful, the selected constructor is called to initialize the object, with the initializer expression or expression-list as its argument(s).
(emphasis mine)
This means that the copy constructor(which is the selected constructor here) will be used/called to initialize the object named a with the expression-list as its argument and since in your copy ctor's member initializer list you're initializing a.v to 1, the output printing 1 of clang is correct.
Suppose we have this code:
class A {
public:
A() = default;
A(const A&) = delete;
~A() = default;
};
class B {
public:
B() : a{} { }
A a[1];
};
int main()
{
B b;
}
This code compiles on latest GCC 9.2, Clang 9.2, and MSVC 19.22.
But when I change A default destructor to ~A() { } GCC returns error use of deleted function 'A::A(const A&)'. Clang and MSVC still compile.
When I write the copy constructor of A, GCC compiles, but at runtime this constructor was never called. What does GCC need the copy constructor for?
Is it GCC bug? (I've tried about all GCC versions on GodBolt.org, same error.)
This is a GCC bug.
The default constructor of B uses aggregate initialization to initialize a with no initializers. [dcl.init.aggr]/8:
If there are fewer initializer-clauses in the list than there are
elements in a non-union aggregate, then each element not explicitly
initialized is initialized as follows:
If the element has a default member initializer ([class.mem]), the element is initialized from that initializer.
Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
Otherwise, the program is ill-formed.
[...]
So a[0] is copy-initialized from {}, which is copy-list-initialization. This is probably where GCC starts to get confused — copy-initialization does not necessarily involve the copy constructor.
[dcl.init.list]/3.4:
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.
[...]
Therefore, the default constructor of A is used directly. There's no copy constructor involved. Nor triviality.
In case you are worrying about the difference between C++11 and C++17, N3337 [dcl.init.aggr]/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 an empty initializer list ([dcl.init.list]).
[...]
Copy-initialization isn't even involved here. And N3337 [dcl.init.list]/3.1:
List-initialization of an object or reference of type T is defined
as follows:
If the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
[...]
No change.
Based on this code
struct Foo
{
Foo()
{
cout << "default ctor" << endl;
}
Foo(std::initializer_list<Foo> ilist)
{
cout << "initializer list" << endl;
}
Foo(const Foo& copy)
{
cout << "copy ctor" << endl;
}
};
int main()
{
Foo a;
Foo b(a);
// This calls the copy constructor again!
//Shouldn't this call the initializer_list constructor?
Foo c{b};
_getch();
return 0;
}
The output is:
default ctor
copy ctor
copy ctor
In the third case, I'm putting b into the brace-initialization which should call the initializer_list<> constructor.
Instead, the copy constructor takes the lead.
Will someone of you tell me how this works and why?
As pointed out by Nicol Bolas, the original version of this answer was incorrect: cppreference at the time of writing incorrectly documented the order in which constructors were considered in list-initialization. Below is an answer using the rules as they exist in the n4140 draft of the standard, which is very close to the official C++14 standard.
The text of the original answer is still included, for the record.
Updated Answer
Per NathanOliver's comment, gcc and clang produce different outputs in this situation:
g++ -std=c++14 -Wall -pedantic -pthread main.cpp && ./a.out
default ctor
copy ctor
copy ctor
initializer list
clang++ -std=c++14 -Wall -pedantic -pthread main.cpp && ./a.out
default ctor
copy ctor
copy ctor
gcc is correct.
n4140 [dcl.init.list]/1
List-initialization is initialization of an object or reference from a braced-init-list.
You're using list-initialization there, and since c is an object, the rules for its list-initialization are defined in [dcl.init.list]/3:
[dcl.init.list]/3:
List-initialization of an object or reference of type T is defined as follows:
If T is an aggregate...
Otherwise, if the initializer list has no elements...
Otherwise, if T is a specialization of std::initializer_list<E>...
going through the list so far:
Foo is not an aggregate.
It has one element.
Foo is not a specialization of std::initializer_list<E>.
Then we hit [dcl.init.list]/3.4:
Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.
Now we're getting somewhere. 13.3.1.7 is also known as [over.match.list]:
Initialization by list-initialization
When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:
Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.
So the copy constructor will only be considered after the initializer list constructors, in the second phase of overload resolution. The initializer list constructor should be used here.
It's worth noting that [over.match.list] then continues with:
If the initializer list has no elements and T has a default constructor, the first phase is omitted. In copy-list initialization, if an explicit constructor is chosen, the initialization is ill-formed.
and that after [dcl.init.list]/3.5 deals with single-element list initialization:
Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element; if a narrowing conversion (see below) is required to convert the element to T, the program is ill-formed.
which explains where cppreference got their special case for single-element list initialization, though they placed it higher in the order than it should be.
Original Answer
You're encountering an interesting aspect of list initialization, where if the list fulfills certain requirements it may be treated like a copy-initialization rather than a list-initialization.
from cppreference:
The effects of list initialization of an object of type T are:
If T is a class type and the initializer list has a single element of
the same or derived type (possibly cv-qualified), the object is
initialized from that element (by copy-initialization for
copy-list-initialization, or by direct-initialization for
direct-list-initialization). (since c++14)
Foo c{b} fulfills all these requirements.
Let us examine what the C++14 specification says about list initialization here. [dcl.init.list]3 has a sequence of rules which are to be applied in order:
3.1 does not apply, since Foo is not an aggregate.
3.2 does not apply, since the list is not empty.
3.3 does not apply, since Foo is not a specialization of initializer_list.
3.4 does apply, since Foo is a class type. It says to consider constructors with overload resolution, in accord with [over.match.list]. And that rule says to check initializer_list constructors first. Since your type has an initilaizer_list constructor, the compiler must check to see if an initializer_list matching one of those constructors can be manufactured from the given values. It can, so that is what must be called.
In short, GCC is right and Clang is wrong.
It should be noted that the C++17 working draft changes nothing about this. It has a new section 3.1 that has special wording for single-value lists, but that only applies to aggregates. Foo is not an aggregate, so it does not apply.
#include<cstddef>
template<typename T, std::size_t N>
struct A {
T m_a[N];
A() : m_a{} {}
};
struct S {
explicit S(int i=4) {}
};
int main() {
A<S, 3> an;
}
The above code compiles fine with MSVC (2017), but fails with clang 3.8.0 (Output of clang++ --version && clang++ -std=c++14 -Wall -pedantic main.cpp):
clang version 3.8.0 (tags/RELEASE_380/final 263969)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/local/bin
main.cpp:6:15: error: chosen constructor is explicit in copy-initialization
A() : m_a{} {}
^
main.cpp:14:13: note: in instantiation of member function 'A<S, 3>::A' requested here
A<S, 3> an;
^
main.cpp:10:14: note: constructor declared here
explicit S(int i=4) {}
^
main.cpp:6:15: note: in implicit initialization of array element 0 with omitted initializer
A() : m_a{} {}
^
1 error generated.
clang 5.0 also refuses to compile this:
<source>:6:17: error: expected member name or ';' after declaration specifiers
A() : m_a{} {}
^
<source>:6:14: error: expected '('
A() : m_a{} {}
^
2 errors generated.
If I use simple parentheses in As constructor to (i.e. A() : m_a() {}), it compiles fine. From cppreference I would have suspected that both should result in the same (i.e. value initialization). Am I missing something or is this a bug in one of the compilers?
Clang is correct.
Your confusion comes from:
From cppreference I would have suspected that both should result in the same (i.e. value initialization).
No they have different effects. Note the notes in that page:
In all cases, if the empty pair of braces {} is used and T is an aggregate type, aggregate-initialization is performed instead of value-initialization.
That means when initialized with braced-init-list, for aggregate type, aggregate-initialization is preferred to be performed. With A() : m_a{} {}, and m_a is an array, which belongs to aggregate type, then aggregate initialization is performed instead:
(emphasis mine)
Each direct public base, (since C++17) array element, or non-static class member, in order of array subscript/appearance in the class definition, is copy-initialized from the corresponding clause of the initializer list.
and
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 initializers, if provided in the class definition, and otherwise (since C++14) by 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).
That means, the remaining elements, i.e. all the 3 elements of m_a will be copy-initialized from the empty list; for empty list the default constructor of S will be considered but it's declared as explicit; the copy-initialization won't invoke explicit constructors:
copy-list-initialization (both explicit and non-explicit constructors are considered, but only non-explicit constructors may be called)
On the other hand, A() : m_a() {} performs value initialization, then
3) if T is an array type, each element of the array is value-initialized;
then
1) if T is a class type with no default constructor or with a user-provided or deleted default constructor, the object is default-initialized;
then the default constructor of S is invoked to initialize the elements of m_a. Whether it's explicit or not doesn't matter for default initialization.
For m_a{}:
[dcl.init]/17.1 sends us to [dcl.init.list], and [dcl.init.list]/3.4 says that we perform aggregate initialization on m_a per [dcl.init.aggr].
The semantics of initializers are as follows. [...]
If the initializer is a (non-parenthesized) braced-init-list or is = braced-init-list, the object or reference is list-initialized.
[...]
List-initialization of an object or reference of type T is defined as follows:
[...]
Otherwise, if T is an aggregate, aggregate initialization is performed.
[...]
[dcl.init.aggr]/5.2 says that we copy-initialize each element of m_a from an empty initializer list, i.e., {}.
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]).
[...]
This sends us back to [dcl.init]/17.1 for the initialization of each element, which again sends us to [dcl.init.list].
This time we hit [dcl.init.list]/3.5, which says that the element is value-initialized.
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.
[...]
Which brings us to [dcl.init]/8.1, which says that the element is default-initialized.
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;
[...]
Which hits [dcl.init]/7.1, which says we enumerate constructors per [over.match.ctor] and perform overload resolution on the initializer ();
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.
[...]
and [over.match.ctor] says:
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, the candidate functions are all the converting
constructors of that class.
This default-initialization is in the context of copy-initialization, so the candidate functions are "all the converting constructors of that class".
The explicit default constructor is not a converting constructor. As a result, there is no viable constructor. Hence overload resolution fails, and the program is ill-formed.
For m_a():
We hit [dcl.init]/17.4, which says that the array is value-initialized.
The semantics of initializers are as follows. [...]
[...]
If the initializer is (), the object is value-initialized.
[...]
Which brings us to [dcl.init]/8.3, which says that each element is value-initialized.
To value-initialize an object of type T means:
[...]
if T is an array type, then each element is value-initialized;
[...]
Which again brings us to [dcl.init]/8.1, and then to [dcl.init]/7.1, and so we again enumerate constructors per [over.match.ctor] and perform overload resolution on the initializer ();
This time, the default-initialization is not in the context of copy-initialization, so the candidate functions are "all the constructors of the class of the object being initialized".
This time, the explicit default constructor is a candidate and selected by overload resolution. So the program is well-formed.
This is explicitly ill-formed by the Standard (the question is, though, why?):
m_a{} list-initializes the S::m_a:
[dcl.init.list]/1
List-initialization is initialization of an object or reference from a braced-init-list.
Such an initializer is called an initializer list, and the comma-separated initializer-clauses of the initializer-list or designated-initializer-clauses of the designated-initializer-list are called the elements of the initializer list. An initializer list may be empty. List-initialization can occur in direct-initialization or copy-initialization contexts; list-initialization in a direct-initialization context is called direct-list-initialization and list-initialization in a copy-initialization context is called copy-list-initialization.
As an array, A<S, 3>::m_a is an aggregate type ([dcl.init.aggr]/1).
[dcl.init.aggr]/3.3
When an aggregate is initialized by an initializer list as specified in [dcl.init.list], [...]
3.3 the initializer list must be {}, and there are no explicitly initialized elements.
following, since there are no explicitly initialized elements:
[dcl.init.aggr]/5.2
For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows: [...]
5.2 if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
Each S of A<S, 3>::m_a is, then, copy-initialized:
[dcl.init]/17.6.3
The semantics of initializers are as follows.
The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression.
If the initializer is not a single (possibly parenthesized) expression, the source type is not defined. [...]
17.6 If the destination type is a (possibly cv-qualified) class type: [...]
17.6.3 Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in [over.match.copy], and the best one is chosen through overload resolution.
If the conversion cannot be done or is ambiguous, the initialization is ill-formed.
Since the default constructor of S is explicit, it cannot convert from the source type to the destination type (S).
The syntax using m_a() is, on the other hand, not aggregate member initialization and does not invoke copy-initialization.
If I understand the standard correctly clang is correct.
According to [dcl.init.aggr]/8.5.1:2
When an aggregate is initialized by an initializer list, as specified
in 8.5.4, the elements of the initializer list are taken as
initializers for the members of the aggregate, in increasing subscript
or member order. Each member is copy-initialized from the
corresponding initializer-clause.
And further down in the same clause [dcl.init.aggr]/8.5.1: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-equal- initializer, from an empty initializer list
According to the rules for list initialization [over.match.list]/13.3.1.7
In copy-list-initialization, if an explicit constructor is chosen, the
initialization is ill-formed.
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{};
}
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
};