Value initialization: MSVC vs clang - c++

#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.

Related

Program with explicit works in msvc but not in gcc

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

Copy-elision in direct initialization from braced-init-list

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.

Are there any difference in empty parentheses (“T()”) and empty braces (“T{}”) when used as initializers?

Generally speaking, parentheses and braces are very different. For minimal reproducible example:
#include <array>
#include <vector>
int main()
{
std::array<int, 2>{42, 42}; // OK
std::array<int, 2>(42, 42); // ill-formed
std::vector<int>{42, 42}; // two elements
std::vector<int>(42, 42); // 42 elements
}
However, since empty braces use value-initialization instead of std::initializer_list constructors, is there any different between empty parentheses and empty braces when used as initializers?
More formally, given a type T, is it possible that T() and T{} are different? (Either may be ill-formed.)
(This question and answer was originally created for C++20 standard compatible vector on Code Review Stack Exchange. It is intended that the answer covers all possible cases. Please inform me if I missed any.)
(The links in this answer point to N4659, the C++17 final draft. However, at the time of this writing, the situation is exactly the same for C++20.)
Yes, it's possible. There are two cases:
Case 1
T is a non-union aggregate for which zero-initialization, followed by default-initialization if the aggregate has a non-trivial constructor, differs from copy-initialization from {}.
We can use std::in_place_t to construct our example, because it has an explicit default constructor. Minimal reproducible example:
#include <utility>
struct A {
std::in_place_t x;
};
int main()
{
A(); // well-formed
A{}; // ill-formed
}
(live demo)
Case 1, variant
T is a union aggregate for whose first element default-initialization differs from copy-initialization from {}.
We can change struct to union in Case 1 to form a minimal reproducible example:
#include <utility>
union A {
std::in_place_t x;
};
int main()
{
A(); // well-formed
A{}; // ill-formed
}
(live demo)
Case 2
T is of the form const U& or U&& where U can be list-initialized from {}.
Minimal reproducible example:
int main()
{
using R = const int&;
R{}; // well-formed
R(); // ill-formed
}
(live demo)
Detailed explanation
T()
Per [dcl.init]/17:
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.
If the initializer is a (non-parenthesized) braced-init-list or is = braced-init-list, the object or reference is list-initialized.
If the destination type is a reference type, see [dcl.init.ref].
If the destination type is an array of characters, an array of char16_­t, an array of char32_­t, or an array of wchar_­t, and
the initializer is a string literal, see [dcl.init.string].
If the initializer is (), the object is value-initialized.
[...]
We can conclude that T() always value-initializes the object.
T{}
Per [dcl.init]/17:
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.
If the initializer is a (non-parenthesized) braced-init-list or is = braced-init-list, the object or reference is
list-initialized.
[...]
That's enough for us to conclude that T{} always list-initializes the object.
Now let's go through [dcl.init.list]/3. I have highlighted the possible cases. The other cases are not possible because they require the initializer list to be non-empty.
List-initialization of an object or reference of type T is defined
as follows:
(3.1) If T is an aggregate class and the initializer list has a single element of type cv U, where U is T or a class derived from
T, the object is initialized from that element (by
copy-initialization for copy-list-initialization, or by
direct-initialization for direct-list-initialization).
(3.2) Otherwise, if T is a character array and the initializer list has a
single element that is an appropriately-typed string literal
([dcl.init.string]), initialization is performed as described in that
section.
(3.3) Otherwise, if T is an aggregate, aggregate initialization is
performed.
(3.4) Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is
value-initialized.
(3.5) Otherwise, if T is a specialization of
std​::​initializer_­list<E>, the object is constructed as described
below.
(3.6) Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best
one is chosen through overload resolution ([over.match],
[over.match.list]). If a narrowing conversion (see below) is required
to convert any of the arguments, the program is ill-formed.
(3.7) Otherwise, if T is an enumeration with a fixed underlying type
([dcl.enum]), the initializer-list has a single element v, and the
initialization is direct-list-initialization, the object is
initialized with the value T(v) ([expr.type.conv]); if a narrowing
conversion is required to convert v to the underlying type of T,
the program is ill-formed.
(3.8) 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 (by copy-initialization for copy-list-initialization, or
by direct-initialization for direct-list-initialization); if a
narrowing conversion (see below) is required to convert the element to
T, the program is ill-formed.
(3.9) Otherwise, if T is a reference type, a prvalue of the type referenced by T is generated. The prvalue initializes its result
object by copy-list-initialization or direct-list-initialization,
depending on the kind of initialization for the reference. The prvalue
is then used to direct-initialize the reference.
(3.10) Otherwise, if the initializer list has no elements, the object is value-initialized.
(3.11) Otherwise, the program is ill-formed.
(Note: (3.6) is not possible in this case, for the following reason: (3.4) covers the case where a default constructor is present. In order for (3.6) to be considered, a non-default constructor has to be called, which is not possible with an empty initializer list. (3.11) is not possible because (3.10) covers all cases.)
Now let's analyze the cases:
(3.3)
For an aggregate, value-initialization first performs zero-initialization and then, if the element has a non-trivial default constructor, default-initialization, on the aggregate, per [dcl.init]/8:
To value-initialize an object of type T means:
[...]
if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is
zero-initialized and the semantic constraints for
default-initialization are checked, and if T has a non-trivial default
constructor, the object is default-initialized;
[...]
Non-union aggregates
When copy-initializing a non-union aggregate from {}, elements that are not explicitly initialized with a default member initializer are copy-initialized from {} per [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.
[...]
See Case 1.
Union aggregates
If the aggregate is a union, and no member has a default member initializer, then copying-initializing the aggregate from {} copy-initializes the first element from {}: [dcl.init.aggr]/8:
[...]
If the aggregate is a union and the initializer list is empty, then
if any variant member has a default member initializer, that member is initialized from its default member initializer;
otherwise, the first member of the union (if any) is copy-initialized from an empty initializer list.
See Case 1, variant.
(3.4)
Value-initialized, so no difference.
(3.9)
T() isn't allowed if T is a reference per [dcl.init]/9:
A program that calls for default-initialization or
value-initialization of an entity of reference type is ill-formed.
See Case 2.
(3.10)
Similarly, value-initialized. No difference.
is there any different between empty parentheses and empty braces when used as initializers
There are cases where empty parentheses cannot be used as initialiser because it would be syntactically ambiguous with a function declaration:
T t(); // function declaration; not initialisation
T t{}; // value initialisation
More formally, given a type T, is it possible that T() and T{} are different?
The ambiguity described above has a case where T() is parsed as pointer to function, known as the Most Vexing Parse:
U t(T()); // function declaration; not initialisation
U t(T{}); // value initialisation, and direct initialisation

Explicit default constructor

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
};

C++14 value-initialization with deleted constructor

I have some misunderstanding:
Let's mark default constructor of struct A as deleted:
struct A
{
A() = delete;
};
The next instruction is well-formed and what's that effect?:
A a{};
From cppreference value initilization:
1) If T is a class type with no default constructor or with a
user-provided default constructor or with a deleted default
constructor, the object is default-initialized.
but then the effect of default initialization is:
If T is a class type, the default constructor is called to provide the
initial value for the new object.
Or it's aggregate initialization?
Thanks!
Your struct A is :
a class type that has:
no user-provided constructors1,
no private or protected non-static data members,
no base classes,
no virtual member functions.
It therefore qualifies as an aggregate type, according to the definition provided by § 8.5.1/1.
Then comes the priority of aggregate initialization over value initialization. The standard says that aggregate initialization has precedence over value intialization (draft N3936, § 8.5.4/3, page 201) (emphasis mine)
List-initialization of an object or reference of type T is defined as follows:
If T is an aggregate, aggregate initialization is performed (8.5.1).
Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
[... more rules...]
(1) As requested in the comments on why a deleted constructor does not count as user-defined, here is what the standard says (draft N3936, § 8.4.2/5, page 198):
A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.
It is well formed. A is an aggregate1, and, according to draft N3936, an empty initializer list used in direct-list initialization of an aggregate results in aggregate initialization:
From § 8.5.4/3 List-initialization [dcl.init.list]:
List-initialization of an object or reference of type T is defined as follows:
— If T is an aggregate, aggregate initialization is performed (8.5.1).
[ Example:
struct S2 { int m1; double m2, m3; };
....
S2 s23{}; // OK: default to 0,0,0
....
— end example ]
....
The relevant changes between C++11 and C++1y are a change in the precedence of aggregate vs. value initialization for the case of aggregates:
C++11 leads with
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.
— Otherwise, if T is an aggregate, aggregate initialization is performed (8.5.1)....
followed by the example above.
C++1y gives priority to aggregate initialization:
List-initialization of an object or reference of type T is defined as follows:
— If T is an aggregate, aggregate initialization is performed (8.5.1).
....
— Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
1 Why is A an aggregate?
It is an aggregate both in C++11 and C++14.
C++1y:
8.5.1 Aggregates [dcl.init.aggr]
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
The only part that is not obvious is whether the defaulted constructor is user-provided or not. It isn't:
In § 8.4.2 [dcl.fct.def.default]:
A function is user-provided if it is user-declared and not explicitly
defaulted or deleted on its first declaration.