#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 A
s 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 membersand bases (since C++17)
are initializedby 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 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".
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 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 you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With