Any difference between copy-list-initialization and traditional copy-initialization? - c++

Except for supporting multiple arguments, disallowing narrowing conversion, matching constructor taking std::initializer_list argument, what else is different for copy-list-initialization against traditional copy-initialization?
To be specific, assume there are two user-defined types, A and B:
class A {...};
class B {...};
B b;
A a1 = {b};
A a2 = b;
What kind of definition of A and B will make a difference on those two forms of initialization? e.g. Is there a certain definition of A and B that will make one of the initialization legal but the other illegal, or both legal but with different semantics, or both illegal with different causes?
(Assume A doesn't have a constructor taking std::initializer_list argument.)
EDIT: Adding a link to a somewhat related question of mine: What is the supposed behavior of copy-list-initialization in the case of an initializer with a conversion operator?

Copy-initialization always considers availability of copy constructors, while copy-list-initialization doesn't.
class B {};
struct A
{
A(B const&) {}
A(A const&) = delete;
};
B b;
A a1 = {b}; // this compiles
A a2 = b; // this doesn't because of deleted copy-ctor
This is because copy-list-initialization is identical to direct-list-initialization except in one situation - had A(B const&) been explicit, the former would've failed, while the latter will work.
class B {};
struct A
{
explicit A(B const&) {}
};
int main()
{
B b;
A a1{b}; // compiles
A a2 = {b}; // doesn't compile because ctor is explicit
}

Probably, the behaviour of the new copy-list-initialization was defined to be "good" and consistent, but the "weird" behaviour of old copy-initialization couldn't be changed because of backward compatibility.
As you can see the rules for list-initialization in this clause are identical for direct and copy forms.
The difference related to explicit is described only in the chapter on overload resolution. But for traditional initialization direct and copy forms are not identical.
The traditional and brace initializations are defined separately, so there's always a potential for some (probably unintended) subtle differences.
The differences I can see from the excerpts of the standard:
1. Already mentioned differences
narrowing conversions are disallowed
multiple arguments are possible
braced syntax prefers initializer-list constructors if they present:
struct A
{
A(int i_) : i (i_) {}
A(std::initializer_list<int> il) : i (*il.begin() + 1) {}
int i;
}
A a1 = 5; // a1.i == 5
A a2 = {5}; // a2.i = 6
2. Different behaviour for aggregates
For aggregates you can't use braced copy-constructor, but can use traditional one.
struct Aggr
{
int i;
};
Aggr aggr;
Aggr aggr1 = aggr; // OK
Aggr aggr2 = {aggr}; // ill-formed
3. Different behaviour for reference initialization in presence of conversion operator
Brace initialization can't use operators of conversion to reference type
struct S
{
operator int&() { return some_global_int;}
};
int& iref1 = s; // OK
int& iref2 = {s}; // ill-formed
4. Some subtle differences in initialization of object of class type by object of other type
These difference are marked by [*] in the excerpts of the Standard at the end of this answer.
Old initialization uses notion of user-defined conversion sequences (and, particularly, requires availability of copy constructor, as was mentioned)
Brace initialization just performs overload resolution among applicable constructors, i.e. brace initialization can't use operators of conversion to class type
These differences are responsible for some not very obvious (for me) cases like
struct Intermediate {};
struct S
{
operator Intermediate() { return {}; }
operator int() { return 10; }
};
struct S1
{
S1(Intermediate) {}
};
S s;
Intermediate im1 = s; // OK
Intermediate im2 = {s}; // ill-formed
S1 s11 = s; // ill-formed
S1 s12 = {s}; // OK
// note: but brace initialization can use operator of conversion to int
int i1 = s; // OK
int i2 = {s}; // OK
5. Difference in overload resolution
Different treatment of explicit constructors
See 13.3.1.7 Initialization by list-initialization
In copy-list-initialization, if an explicit constructor is chosen, the
initialization is ill-formed. [ Note: This differs from other
situations (13.3.1.3, 13.3.1.4), where only converting constructors
are considered for copy initialization. This restriction only applies
if this initialization is part of the final result of overload
resolution. — end note ]
If you can see more differences or somehow correct my answer (including grammar mistakes), please do.
Here are the relevant (but long) excerpts from the current draft of the C++ standard (I haven't found a way to hide them under spoiler):
All of them are located in the chapter 8.5 Initializers
8.5 Initializers
If the initializer is a (non-parenthesized) braced-init-list, the
object or reference is list-initialized (8.5.4).
If the destination type is a reference type, see 8.5.3.
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 8.5.2.
If the initializer is (), the object is
value-initialized.
Otherwise, if the destination type is an array,
the program is ill-formed.
If the destination type is a (possibly
cv-qualified) class type:
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 (13.3.1.3), and
the best one is chosen through overload resolution (13.3). The
constructor so selected is called to initialize the object, with the
initializer expression or expression-list as its argument(s). If no
constructor applies, or the overload resolution is ambiguous, the
initialization is ill-formed.
[*] 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 13.3.1.4, and the best one is
chosen through overload resolution (13.3). If the conversion cannot be
done or is ambiguous, the initialization is ill-formed. The function
selected is called with the initializer expression as its argument; if
the function is a constructor, the call initializes a temporary of the
cv-unqualified version of the destination type. The temporary is a
prvalue. The result of the call (which is the temporary for the
constructor case) is then used to direct-initialize, according to the
rules above, the object that is the destination of the
copy-initialization. In certain cases, an implementation is permitted
to eliminate the copying inherent in this direct-initialization by
constructing the intermediate result directly into the object being
initialized; see 12.2, 12.8.
Otherwise, if the source type is a
(possibly cv-qualified) class type, conversion functions are
considered. The applicable conversion functions are enumerated
(13.3.1.5), and the best one is chosen through overload resolution
(13.3). The user-defined conversion so selected is called to convert
the initializer expression into the object being initialized. If the
conversion cannot be done or is ambiguous, the initialization is
ill-formed.
Otherwise, the initial value of the object being
initialized is the (possibly converted) value of the initializer
expression. Standard conversions (Clause 4) will be used, if
necessary, to convert the initializer expression to the cv-unqualified
version of the destination type; no user-defined conversions are
considered. If the conversion cannot be done, the initialization is
ill-formed.
8.5.3 References ...
8.5.4 List-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.
Otherwise, if T is a specialization of
std::initializer_list<E>, a prvalue initializer_list object is
constructed as described below and used to initialize the object
according to the rules for initialization of an object from a class of
the same type (8.5).
[*] 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.
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.
Otherwise, if
T is a reference type, a prvalue temporary of the type referenced by T
is copy-list-initialized or direct-list-initialized, depending on the
kind of initialization for the reference, and the reference is bound
to that temporary. [ Note: As usual, the binding will fail and the
program is ill-formed if the reference type is an lvalue reference to
a non-const type. — end note ]
Otherwise, if the initializer list
has no elements, the object is value-initialized.
Otherwise, the program is ill-formed.

Related

Copy constructor elision for direct initialization when the argument is converted to the destination type

This question is about the wording of the c++ standard.
All compilers, and I think this is what should be, elide the copy constructor for the initialization of the object b bellow (assembly here):
struct B;
struct A{
operator B();
};
struct B{
B(const B&);
B(B&&);
};
void test(A a){
B b(a);
}
But when I read the standard, I do not see why this elision shall happen (bold mine) [dcl.init]/17.6.2:
Otherwise, if the initialization is direct-initialization, [...], constructors are considered.
The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution ([over.match]).
The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s).
If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
It is specifically said that the constructor is called. But no compiler does it.
I imagine I am missing something or not reading correctly the standard. How should I read the standard?
This contrast with the previous and next paragraphs of the standard that specifically mandate copy elision [dcl.init]/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.
and [dlc.init]/17.6.3:
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversions that can convert from the source type to the destination type [...]
The function selected is called with the initializer expression as its argument; [...]
The call is used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization.
Where the last sentence send me back to [dcl.init]/17.6.1 which would also implies the copy elision.
#T.C. answered in a comment, this is core langugage issue CWG2327.

What changes to C++ made copy initialization work for class with explicit constructor?

Consider this code:
struct X{
explicit X(){}
explicit X(const X&){}
};
void foo(X a = X()){}
int main(){}
Using C++14 standard, both GCC 7.1 and clang 4.0 rejects the code, which is what I expected.
However, using C++17 (-std=c++1z), they both accept the code. What rule changed?
For both compilers to exhibit this same behavior, I doubt this to be a bug. But as far as I can tell, the latest draft still says, default argument uses the semantics of copy-initialization 1. Again, we know that explicit constructors will only allow direct initialization 2.
1: dcl.fct.default/5;
2: class.conv.ctor/2
Because the behavior of copy elision changes from C++17; for this case copy elision is mandatory.
Mandatory elision of copy/move operations
Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible:
In the initialization of an object, when the initializer expression is
a prvalue of the same class type (ignoring cv-qualification) as the
variable type:
T f() {
return T();
}
T x = T(T(f())); // only one call to default constructor of T, to initialize x
Note: the rule above does not specify an optimization: C++17 core language specification of prvalues and temporaries is fundamentally different from that of the earlier C++ revisions: there is no longer a temporary to copy/move from. Another way to describe C++17 mechanics is "unmaterialized value passing": prvalues are returned and used without ever materializing a temporary.
And for copy initialization:
The effects of copy initialization are:
First, if T is a class type and the initializer is a prvalue
expression whose cv-unqualified type is the same class as T, the
initializer expression itself, rather that a temporary materialized
from it, is used to initialize the destination object: see copy
elision (since C++17)
If T is a class type and the cv-unqualified version of the type of
other is T or a class derived from T, the non-explicit constructors of
T are examined and the best match is selected by overload resolution.
The constructor is then called to initialize the object.
That means for X a = X(), a will be default constructed directly, the copy/move constructors and their side effects will be omiited completely. The selection of non-explicit constructors for overload resolution won't take place, which is required in C++14 (and before). For these guaranteed cases, the copy/move constructors don't participate in, then it won't matter whether they're explicit or not.
The most important for the example in the question rule is [expr.type.conv]/2. But lets start from [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.
...
(17.6) — If the destination type is a (possibly cv-qualified) class type:
— 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]
So, in X a = X(), the initializer expression X() is used to initialize the destination object. Of course, this is not enough to answer: why default constructor is selected (i.e. how X() becomes ()) and why explicit default constructor is fine.
The X() expression is explicit type conversion in functional notation, so lets look into [expr.type.conv]/2:
If the initializer is a parenthesized single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression. If the type is cv void and the initializer is (), the expression is a prvalue of the specified type that performs no initialization. Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized with the initializer.
Emphasis of the relevant sentence is mine. It says that for X():
the object is initialized with () (it is "the initializer" by [expr.type.conv]/1), that's why the default constructor is selected;
the object is direct-initialized, that's why it is OK that the default constructor is explicit.
In more details: when the initializer is (), [dcl.init]/(17.4) apply:
If the initializer is (), 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]/1
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.
In C++14, [dcl.init](17.6) was saying:
— If the destination type is a (possibly cv-qualified) class type:
— 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]).
So for X a = X(), only converting (non-explicit) constructors accepting one argument of type X will be considered (which are copy and move constructors).

constexpr array of constexpr objects using move ctor

I have a class with a constexpr value constructor, but no copy or move ctor
class C {
public:
constexpr C(int) { }
C(const C&) = delete;
C& operator=(const C&) = delete;
};
int main() {
constexpr C arr[] = {1, 2};
}
I've found that this code doesn't work because it's actually trying to use the move constructor for C rather than the value constructor to construct in place. One issue is that I want this object to be unmovable (for test purposes) but I thought "okay, fine, I'll add a move constructor."
class C {
public:
constexpr C(int) { }
C(const C&) = delete;
C& operator=(const C&) = delete;
C& operator=(C&&) = delete;
C(C&&) { /*something*/ } // added, assume this must be non trivial
};
Okay fine, now it uses the move constructor and everything works under gcc but when I use clang, it complains because the move constructor is not marked constexpr
error: constexpr variable 'arr' must be initialized by a constant expression
constexpr C arr[] = {1, 2};
If I mark the move constructor constexpr it works under gcc and clang, but the issue is that I want to have code in the move constructor if it runs at all, and constexpr constructors must have empty bodies. (The reason for my having code in the move ctor isn't worth getting into).
So who is right here? My inclination is that clang would be correct for rejecting the code.
NOTE
It does compile with initializer lists and non-copyable non-movable objects as below:
class C {
public:
constexpr C(int) { }
C(const C&) = delete;
C& operator=(const C&) = delete;
C& operator=(C&&) = delete;
C(C&&) = delete;
};
int main() {
constexpr C arr[] = {{1}, {2}};
}
My main concern is which compiler above is correct.
So who is right here?
Clang is correct in rejecting the code. [expr.const]/2:
A conditional-expression e is a core constant expression unless
the evaluation of e, following the rules of the abstract machine
(1.9), would evaluate one of the following expressions:
an invocation of a function other than a constexpr constructor for a literal class, a constexpr function, or an implicit invocation
of a trivial destructor (12.4)
Clearly your move constructor isn't a constexpr constructor - [dcl.constexpr]/2
Similarly, a constexpr specifier used in a constructor declaration
declares that constructor to be a constexpr constructor.
And the requirements for an initializer of a constexpr object are in [dcl.constexpr]/9:
[…] every full-expression that appears in its initializer shall be a
constant expression. [ Note: Each implicit conversion used in
converting the initializer expressions and each constructor call used
for the initialization is part of such a full-expression. — end note
]
Finally the move constructor is invoked by the copy-initialization of the array elements with the corresponding initializer-clauses - [dcl.init]:
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 13.3.1.4,
and the best one is chosen through overload resolution (13.3). If the
conversion cannot be done or is ambiguous, the initialization is
ill-formed. The function selected is called with the initializer
expression as its argument; if the function is a constructor, the
call initializes a temporary of the cv-unqualified version of the
destination type. The temporary is a prvalue. The result of the call
(which is the temporary for the constructor case) is then used to
direct-initialize, according to the rules above, the object that is
the destination of the copy-initialization.
In the second example, copy-list-initialization applies - and no temporary is introduced.
By the way: GCC 4.9 does not compile the above, even without any warning flags provided.
§8.5 [dcl.init]/p17:
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, the object or reference is list-initialized (8.5.4).
[...]
If the destination type is a (possibly cv-qualified) class type:
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, [...]
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 13.3.1.4,
and the best one is chosen through overload resolution (13.3). If the
conversion cannot be done or is ambiguous, the initialization is
ill-formed. The function selected is called with the initializer
expression as its argument; if the function is a constructor, the call
initializes a temporary of the cv-unqualified version of the
destination type. The temporary is a prvalue. The result of the call
(which is the temporary for the constructor case) is then used to
direct-initialize, according to the rules above, the object that is
the destination of the copy-initialization. In certain cases, an
implementation is permitted to eliminate the copying inherent in this
direct-initialization by constructing the intermediate result directly
into the object being initialized; see 12.2, 12.8.
[...]
§8.5.1 [dcl.init.aggr]/p2:
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. If the initializer-clause is an
expression and a narrowing conversion (8.5.4) is required to convert
the expression, the program is ill-formed. [ Note: If an
initializer-clause is itself an initializer list, the member is
list-initialized, which will result in a recursive application of the
rules in this section if the member is an aggregate. —end note ]
§8.5.4 [dcl.init.list]/p3:
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 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.
[...]
For constexpr C arr[] = {1, 2};, aggregate initialization copy-initializes each element from the corresponding initializer-clause, i.e., 1 and 2. As described in §8.5 [dcl.init]/p17, this constructs a temporary C and then direct-initializes the array element from the temporary, which requires an accessible copy or move constructor. (The copy/move can be elided, but the constructor must still be available.)
For constexpr C arr[] = {{1}, {2}};, the elements are copy-list-initialized instead, which does not construct temporaries (note the absence of any mention of a temporary being constructed in §8.5.4 [dcl.init.list]/p3).

Is there a case missing in §8.5 p17 (semantics of initializers) of N3797?

For the aggregate
struct S{int i, j;};
the declarations S s({1, 2}); and S s({1}); perform direct-initialization according to N3797 §8.5 p16:
The initialization that occurs in the forms
T x(a);
T x{a};
as well as in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization.
But §8.5 p17 doesn't seem to characterize them:
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, the object or reference is list-initialized (8.5.4).
If the destination type is a reference type, see 8.5.3.
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 8.5.2.
If the initializer is (), the object is value-initialized.
Otherwise, if the destination type is an array, the program is ill-formed.
If the destination type is a (possibly cv-qualified) class type:
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 (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
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 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified
version of the destination type. The temporary is a prvalue. The result of the call (which is the
temporary for the constructor case) is then used to direct-initialize, according to the rules above,
the object that is the destination of the copy-initialization. In certain cases, an implementation
is permitted to eliminate the copying inherent in this direct-initialization by constructing the
intermediate result directly into the object being initialized; see 12.2, 12.8.
Otherwise, if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated (13.3.1.5), and the best one is chosen through overload resolution (13.3). The user-defined conversion so selected is called to convert the initializer expression into the object being initialized. If the conversion cannot be done or is ambiguous, the initialization is ill-formed.
Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. Standard conversions (Clause 4) will be used, if necessary, to convert the initializer expression to the cv-unqualified version of the destination type; no user-defined conversions are considered. If the conversion cannot be done, the initialization is ill-formed. [Note: An expression of type “cv1 T” can initialize an object of type “cv2 T” independently of the cv-qualifiers cv1 and cv2.
int a;
const int b = a;
int c = b;
— end note ]
The subject declarations, S s({1, 2}); and S s({1});:
are not list-initialization, since each initializer is a parenthesized braced-init-list.
the destination types are not references
the destination types are not array of characters, in general.
the initializers are not ()
the destination types are not arrays.
This case is covered in the Standard: It's §8.5/17 6th bullet (emphasis mine):
If the destination type is a (possibly cv-qualified) class type:
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 (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
Explanation: We first note that S is an aggregate (by §8.5.1/1). But an aggregate is also a class and hence has an implicitely declared defaulted copy/move constructor (by §12.8). Both constructors take one argument and hence are viable (by §13.3.2). Their signatures are as usual:
S(const S&) //copy
S(S&&) //move
Now we have to determine the conversion sequence to convert the initializer-list {1,2} to the parameter types. §13.3.3.1.5/6 states:
Otherwise, if the parameter is a reference, see 13.3.3.1.4. [ Note: The rules in this section will apply for initializing the underlying temporary for the reference. —end note ]
Since the parameter types are references, §13.3.3.1.4/2 applies:
When a parameter of reference type is not bound directly to an argument expression, the conversion sequence is the one required to convert the argument expression to the underlying type of the reference according to 13.3.3.1. Conceptually, this conversion sequence corresponds to copy-initializing a temporary of the underlying type with the argument expression. Any difference in top-level cv-qualification is subsumed by
the initialization itself and does not constitute a conversion.
Since S is an aggregate, we have to apply §13.3.3.1.5/5 to initialize this temporary object:
Otherwise, if the parameter has an aggregate type which can be initialized from the initializer list according to the rules for aggregate initialization (8.5.1), the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion.
Hence, we finally arrive at aggregate initialization of this temporary object. To determine which of these two constructors is the best viable function one has to consult §13.3.3.2 (which is left to the reader). Since the reference is bound to a temporary object the move constructor will actually be chosen.
The declarations S s({1, 2}); and S s({1}); perform direct-initialization according to N3797 §8.5 p16
Well, yes and no. In the first case, and similarly in the second, an S x-value object is constructed with {1, 2} and then passed to the implicitly-declared copy constructor of the s l-value.
This is not the case §8.5 p16 is discussing, because the initialization is not in the form:
S s(a);
S s{a};
unless the standard consider a to be anything. And even then, it's not part of the rest of the cases he defines right after (new, static_cast, etc..).
The subject declarations, S s({1, 2}); and S s({1}); are not list-initialization, since each initializer is a parenthesized braced-init-list.
The expressions {1, 2} and {1} are definitely list-initializations of temporary/expiring S objects.

copy-list-initialization of non-copyable types

12.6.1 - Explicit initialization
struct complex {
complex();
complex(double);
complex(double,double);
};
complex sqrt(complex,complex);
complex g = { 1, 2 }; // construct complex(1, 2)
// using complex(double, double)
// and *copy/move* it into g
8.5 Initializers
14 - The initialization that occurs in the form
T x = a;
as well as in argument passing, function return, throwing an exception
(15.1), handling an exception (15.3), and aggregate member
initialization (8.5.1) is called copy-initialization. [Note:
Copy-initialization may invoke a move (12.8). — end note ]
15 - The initialization that occurs in the forms
T x(a);
T x{a};
as well as in new expressions (5.3.4), static_cast expressions
(5.2.9), functional notation type conversions (5.2.3), and base and
member initializers (12.6.2) is called direct-initialization.
8.5.4 List-initialization [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 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.
The problem with atomics
29.6.5 Requirements for operations on atomic types [atomics.types.operations.req]
#define ATOMIC_VAR_INIT(value) see below
The macro expands to a token sequence suitable for constant
initialization of an atomic variable of static storage duration of a
type that is initialization-compatible with value. [Note: This
operation may need to initialize locks. — end note ] Concurrent access
to the variable being initialized, even via an atomic operation,
constitutes a data race. [ Example:
atomic<int> v = ATOMIC_VAR_INIT(5);
According to previous sections it seems there shouldn't be assignment initialization without a copy-constructor involved, even if it's elided according to §12.8.31 and §12.8.32, but atomics are defined as:
29.5 Atomic types [atomics.types.generic]
atomic() noexcept = default;
constexpr atomic(T) noexcept;
atomic(const atomic&) = delete;
atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;
T operator=(T) volatile noexcept;
T operator=(T) noexcept;
There's no copy-constructor!
Frequently, ATOMIC_VAR_INIT expands to a brace expression for brace initialization, but atomic<int> v = {5} is still an assignment initialization and would imply copy construction after direct construction of a temporary.
I've looked over the "constant initialization" section to see whether there's a loophole allowing this without a copy (because of "The macro expands to a token sequence suitable for constant initialization of an atomic variable of static storage duration of a type that is initialization-compatible with value") but I'm already giving up.
Related discussions:
http://thread.gmane.org/gmane.comp.lib.qt.devel/8298
http://llvm.org/bugs/show_bug.cgi?id=14486
EDIT
An answer quoting the relevant standard sections while building a deduction process would be ideal.
CONCLUSION
So, after the nice answer by Nicol Bolas, the funny conclusion is that complex g = { 1, 2 } is a copy (it is copy-initialization context) which don't copy (copy-list-initialization resolves like direct-list-initialization) for which the standard suggests there's a copy operation (12.6.1: ...and copy/move it into g).
FIX
Pull request: https://github.com/cplusplus/draft/pull/37
complex g = { 1, 2 }; // construct complex(1, 2)
// using complex(double, double)
// and *copy/move* it into g
This is untrue. And I'm not saying that the copy/move will be elided; I mean that there will be no copying or moving.
You quoted 8.5 p14, which defines T x = a; as copy-initialization. This is true. But it then goes on to define how initialization actually works:
From 8.5, p16:
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, the object or reference is list-initialized (8.5.4).
That right there means that copy-initialization rules do not apply to a braced-init-list. They use a separate set of rules, as covered in 8.5.4.
You quoted 8.5.4, which defines T x = {...}; as copy-list-initialization. Where your reasoning goes wrong is that you never looked up what copy-list-initialization actually does. There is no copying; that's just what it's called.
copy-list-initialization is a subset of list-initialization. Therefore, it follows all of the rules laid down by 8.5.4, p3. I'm not going to quote them here, because they're several pages long. I'll simply explain how the rules apply to complex g = {1, 2};, in order:
The initializer list has elements, so this rule doesn't count.
complex is not an aggregate, so this rule doesn't count.
complex is not a specialization of initializer_list, so this rule doesn't count.
Applicable constructors are considered via overload resolution, in accord with the rules of 13.3 and 13.3.1.7. This finds the constructor that takes two doubles.
Therefore, no temporary will be created and copied/moved in.
The only difference between copy-list-initialization and direct-list-initialization is stated in 13.3.1.7, p1:
[...] In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.
That is the only difference between complex g{1, 2} and complex g = {1, 2}. They are both examples of list-initialization, and they work in a uniform way except for the use of explicit constructors.
The constructor-from-T is not explicit, and copy-list-initialization is not the same as copy-initialization. Both cause "constructors to be considered", but copy-initialization always "considers" the copy con­struc­tor, while list-initialization considers constructors with the list elements filled in (plus some details). To wit:
struct Foo
{
Foo(int) {}
Foo(Foo const &) = delete;
};
int main()
{
Foo f = { 1 }; // Fine
}
(This would fail if the constructor was explicit. Also, Foo x = 1; will of course fail on account of the deleted copy constructor.)
Perhaps an even more enlightening use case:
Foo make() { return { 2 }; }
void take(Foo const &);
take(make());
Everything necessary for this is in 8.5.4/3 and in 13.3.1.7/1.