Ambiguous function call in msvc and clang but not in gcc - c++

I wonder which compiler is compliant with the standard, i use the following code
#include <iostream>
#include <string>
#include <memory>
#include <vector>
class AbstractBase
{
public:
virtual ~AbstractBase() {};
virtual std::string get_name() = 0;
virtual int get_number() = 0;
};
class BaseImpl : public AbstractBase
{
public:
BaseImpl() = delete; //(a)
//BaseImpl(BaseImpl&) = delete; //(b)
BaseImpl(const std::vector<std::string>& name_) : name(name_) {}
std::string get_name() override {return name.empty() ? std::string("empty") : name.front();}
private:
std::vector<std::string> name{};
};
class impl : public BaseImpl
{
public:
impl() : BaseImpl({}) {}
int get_number() override {return 42;}
};
int main()
{
std::unique_ptr<AbstractBase> intance = std::make_unique<impl>();
std::cout << intance->get_name() << " " << intance->get_number() << "\n";
return 0;
}
msvc and clang produce a compiler error while gcc is fine with this code. Link to godbold
https://godbolt.org/z/ETYGn5T1h
If one explicit delete the copy ctor of BaseImpl (uncomment (b)) all three compiler are fine. Also if one do not explicitly delete (a) the standard ctor (then no standard ctor is generated because there is an user defined ctor), all compilers are fine.
Apparently clang and msvc believe that by : BaseImpl({}) they can either call the user defined ctor, or indirectly via the default ctor the implicit copy ctor. However, since the default ctor is deleted, this ambiguity should not exist at all. However, by explicitly deleting the default ctor, the compilers seem to first assume that a default ctor exists for BaseImpl and generate an error before checking whether it is deleted.
I wonder now if this behavior is standard compliant.
Edit: I'm attaching the compiler output for the case that one don't want to or can't click on the link to the compiler explorer:

Clang and MSVC are correct, and gcc has a bug.
Defining a function as deleted is not the same as not declaring the function.
Except for move constructors, move assignment functions, and some cases of inherited constructors (see [over.match.funcs]/8), a deleted function is considered to exist for purposes of overload resolution. Nothing else in section [over] treats a deleted function specially. And we have [over.best.ics]/2, emphasis mine:
Implicit conversion sequences are concerned only with the type, cv-qualification, and value category of the argument and how these are converted to match the corresponding properties of the parameter. [ Note: Other properties, such as the lifetime, storage class, alignment, accessibility of the argument, whether the argument is a bit-field, and whether a function is deleted, are ignored. So, although an implicit conversion sequence can be defined for a given argument-parameter pair, the conversion from the argument to the parameter might still be ill-formed in the final analysis. — end note ]
So within impl() : BaseImpl({}) {}, the BaseImpl initializer uses overload resolution to select the BaseImpl constructor used to initialize the base class subobject. The candidates are all the constructors of BaseImpl: the intended BaseImpl(const std::vector<std::string>&), the deleted BaseImpl(), the implicitly declared copy constructor BaseImpl(const BaseImpl&), and the implicitly declared (and not deleted) move constructor BaseImpl(BaseImpl&&). At this point, BaseImpl() is not viable since the initializer has one argument. The vector constructor is viable since there is a constructor vector(std::initializer_list<std::string>) which is not explicit and can convert the {} argument to the vector type. The copy and move constructors are also viable since the BaseImpl() constructor is declared, and is not explicit, and "can" convert the {} argument to type BaseImpl. So overload resolution is ambiguous, even though some of the implicit conversion sequences use a deleted function.
When the BaseImpl() = delete; declaration is not present, BaseImpl simply doesn't have any default constructor, since the BaseImpl(const std::vector<std::string>&) declaration prevents implicit declaration of a default constructor. So there is no implicit conversion sequence for {} to BaseImpl, and the copy and move constructors of BaseImpl are not viable for the initialization BaseImpl({}). The vector constructor is the only viable function.
When you declare BaseImpl(BaseImpl&), deleted or not, this is considered a copy constructor (despite missing the usual const), so it prevents the implicit declarations of the copy constructor and move constructor. But this copy constructor is not viable for BaseImpl({}), since the reference to non-const type can't bind to the rvalue temporary BaseImpl object involved in using BaseImpl() (see [over.ics.ref]/3). So only the intended vector constructor is viable.

WIP marker
A comment by #aschepler has thrown the conclusion in doubt. See there for details or to pitch in if you're a language lawyer. I'll have a closer look tonight, then update this answer.
TL;DR
GCC is correct in a quite non-obvious way.
What is happening
The clang error message seems pretty clear:
<source>:28:14: error: call to constructor of 'BaseImpl' is ambiguous
impl() : BaseImpl({}) {}
^ ~~
<source>:14:7: note: candidate constructor (the implicit move constructor)
class BaseImpl : public AbstractBase
^
<source>:14:7: note: candidate constructor (the implicit copy constructor)
<source>:19:5: note: candidate constructor
BaseImpl(const std::vector<std::string>& name_) : name(name_) {}
^
1 error generated.
Compiler returned: 1
There are three constructors that are available for the BaseImpl({}) call -- the generated copy and move constructors, and the vector constructor. Clang does not know which of these to choose.
MSVCs error is a bit less straightforward:
x64 msvc v19.latest (Editor #1)
x64 msvc v19.latest
x64 msvc v19.latest
/std:c++20
123
<Compilation failed>
# For more information see the output window
x64 msvc v19.latest - 2681ms
Output of x64 msvc v19.latest (Compiler #1)
example.cpp
<source>(28): error C2259: 'BaseImpl': cannot instantiate abstract class
<source>(14): note: see declaration of 'BaseImpl'
<source>(28): note: due to following members:
<source>(28): note: 'int AbstractBase::get_number(void)': is abstract
<source>(11): note: see declaration of 'AbstractBase::get_number'
Compiler returned: 2
What happens here is that MSVC attempts to call the copy or move constructor, that for this it attempts to create a temporary of type BaseImpl from the initializer {}, and that this fails because BaseImpl is abstract before it fails because the default constructor is deleted (so you don't get an error message about that).
GCC does not consider the copy and move ctors even if they are explicitly added and so just constructs a vector and compiles fine.
What should happen
Let's dive into the standard. In particular, let's have a look at [dcl.init.general]. I'll omit non-matching parts of the standard language and denote that with (...).
First note that according to [dcl.init.general] (15)
15 The initialization that occurs
(15.1) — for an initializer that is a parenthesized expression-list or a braced-init-list,
(...)
is called direct-initialization.
There follows in [dcl.init.general] (16) a long list of conditions for what happens in initialization. The relevant here is (16.6)
(16.6) — Otherwise, if the destination type is a (possibly cv-qualified) class type:
(...)
(16.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 (12.4.2.4), and the best one is chosen through overload resolution (12.4). Then:
(16.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).
(...)
(16.6.2.3) — Otherwise, the initialization is ill-formed.
What this boils down to is: we look for all applicable constructors and attempt to choose a correct one. If that works, we use it, otherwise it's an error.
So let's take a look at overload resolution in [over.match.ctor]. Here it states that
1 When objects of class type are direct-initialized (9.4), (...), 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. (...). The argument list is the expression-list or assignment-expression of the initializer.
So our set of candidate functions are the generated copy and move ctors as well as the vector ctor. Next step is checking which of these are viable according to [over.match.viable]. This means first checking that the number of arguments in the call fits the candidate functions (true for all candidates) and then that
4 Third, for F to be a viable function, there shall exist for each argument an implicit conversion sequence (12.4.4.2) that converts that argument to the corresponding parameter of F. If the parameter has reference type, the implicit conversion sequence includes the operation of binding the reference, and the fact that an lvalue reference to non-const cannot be bound to an rvalue and that an rvalue reference cannot be bound to an lvalue can affect the viability of the function (see 12.4.4.2.5).
An implicit conversion sequence is, according to [over.best.ics.general],
3 A well-formed implicit conversion sequence is one of the following forms: >
(3.1) — a standard conversion sequence (12.4.4.2.2),
(3.2) — a user-defined conversion sequence (12.4.4.2.3), or
(3.3) — an ellipsis conversion sequence (12.4.4.2.4).
where a standard conversion sequence is chiefly concerned with stuff like int to long, lvalue to rvalue, ref to const ref etc. We're interested in user-defined conversion sequences here, which are
1 A user-defined conversion sequence consists of an initial standard conversion sequence followed by a user- defined conversion (11.4.8) followed by a second standard conversion sequence. If the user-defined conversion is specified by a constructor (11.4.8.2), the initial standard conversion sequence converts the source type to the type of the first parameter of that constructor. (...)
2 The second standard conversion sequence converts the result of the user-defined conversion to the target type for the sequence; any reference binding is included in the second standard conversion sequence. (...).
(...)
There is quite definitely a user-defined conversion sequence from {} to std::vector<std::string>. Because BaseImpl's default constructor is deleted, there is not a user-defined conversion sequence from {} to BaseImpl; this would require two user-defined conversions: one to std::vector<std::string> and another to BaseImpl.
So of the three candidate constructors, only the std::vector<std::string> constructor is viable and eligible for overload resolution and should be chosen. GCC does this, and unless I made an error in my analysis, MSVC and clang have a bug.

Related

Understanding copy-initialisation and implicit conversions

I am having trouble understanding why the following copy-initialization doesn't compile:
#include <memory>
struct base{};
struct derived : base{};
struct test
{
test(std::unique_ptr<base>){}
};
int main()
{
auto pd = std::make_unique<derived>();
//test t(std::move(pd)); // this works;
test t = std::move(pd); // this doesn't
}
A unique_ptr<derived> can be moved into a unique_ptr<base>, so why does the second statement work but the last does not? Are non-explicit constructors not considered when performing a copy-initialization?
The error from gcc-8.2.0 is:
conversion from 'std::remove_reference<std::unique_ptr<derived, std::default_delete<derived> >&>::type'
{aka 'std::unique_ptr<derived, std::default_delete<derived> >'} to non-scalar type 'test' requested
and from clang-7.0.0 is
candidate constructor not viable: no known conversion from 'unique_ptr<derived, default_delete<derived>>'
to 'unique_ptr<base, default_delete<base>>' for 1st argument
Live code is available here.
A std::unique_ptr<base> is not the same type as a std::unique_ptr<derived>. When you do
test t(std::move(pd));
You call std::unique_ptr<base>'s conversion constructor to convert pd into a std::unique_ptr<base>. This is fine as you are allowed a single user defined conversion.
In
test t = std::move(pd);
You are doing copy initialization so so you need to convert pd into a test. That requires 2 user defined conversions though and you can't do that. You first have to convert pd to a std::unique_ptr<base> and then you need to convert it to a test. It's not very intuitive but when you have
type name = something;
whatever something is needs to be only a single user defined conversion from the source type. In your case that means you need
test t = test{std::move(pd)};
which only uses a single implicit user defined like the first case does.
Lets remove the std::unique_ptr and look at in a general case. Since std::unique_ptr<base> is not the same type as a std::unique_ptr<derived> we essentially have
struct bar {};
struct foo
{
foo(bar) {}
};
struct test
{
test(foo){}
};
int main()
{
test t = bar{};
}
and we get the same error because we need to go from bar -> foo -> test and that has one user defined conversion too many.
The semantics of initializers are described in [dcl.init] ¶17. The choice of direct initialization vs copy initialization takes us into one of two different bullets:
If the destination type is a (possibly cv-qualified) class type:
[...]
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. 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
[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. The function selected is called with the
initializer expression as its argument; if the function is a
constructor, the call is a prvalue of the cv-unqualified version of
the destination type whose result object is initialized by the
constructor. The call is used to direct-initialize, according to the
rules above, the object that is the destination of the
copy-initialization.
In the direct initialization case, we enter the first quoted bullet. As detailed there, constructors are considered and enumerated directly. The implicit conversion sequence that is required is therefore only to convert unique_ptr<derived> to a unique_ptr<base> as a constructor argument.
In the copy initialization case, we are not directly considering constructors anymore, but rather trying to see which implicit conversion sequence is possible. The only one available is unique_ptr<derived> to a unique_ptr<base> to a test. Since an implicit conversion sequence can contain only one user defined conversion, this is not allowed. As such the initialization is ill-formed.
One could say that using direct initialization sort of "bypasses" one implicit conversion.
Pretty sure that only single implicit conversion is allowed to be considered by the compiler. In first case only conversion from std::unique_ptr<derived>&& to std::unique_ptr<base>&& is required, in the second case the base pointer would also need to be converted to test (for default move constructor to work).
So for example converting the derived pointer to base: std::unique_ptr<base> bd = std::move(pd) and then move assigning it would work as well.

I think there might be a small inaccuracy in bullet point (17.6.2) in §8.5[dcl.init]/17 in N4140

In bullet point (17.6.2) in §8.5[dcl.init]/17 in N4140 we have (emphasis is mine):
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.
The portion of the text in bold seems to indicate that direct-initializations will never invoke a user-defined conversion sequence. But that's not what I found out below:
#include <iostream>
struct A {
A() { std::cout << "default ctor A" << '\n'; }
A(const A&) { std::cout << "copy A" << '\n'; }
};
struct C {
C() { std::cout << "default ctor C" << '\n'; }
operator A() { std::cout << "C::operator A()" << '\n'; return A(); };
};
int main()
{
C c;
A a{c}; // direct-initialization where the C::operator A() is invoked
// to copy construct the object `a`.
}
The following is printed by this snippet:
default ctor C
C::operator A()
default ctor A
copy A
copy A
See live example
Edit
In response to #Johannes answer, please consider A a(c); instead of A a{c};. The rest remains valid, as far as I can understand.
When discussing direct initialization (emphasis is my own):
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.
This line leads to section §8.5 (2.8,2.9) on Overload Resolutions which describes the process.
Overload resolution selects the function to call in seven distinct contexts within the language:
...
(2.4) — invocation of a constructor for direct-initialization (8.5) of a class object...
But, once the candidate functions and argument lists have been identified, the selection of the best function
is the same in all cases:
(2.8) — First, a subset of the candidate functions (those that have the proper number of arguments and meet
certain other conditions) is selected to form a set of viable functions (13.3.2).
(2.9) — Then the best viable function is selected based on the implicit conversion sequences (13.3.3.1) needed
to match each argument to the corresponding parameter of each viable function.
Given the above, direct initialization does in fact go through the process of selecting candidate constructors (in your case, the one parameter constructor A::A( A const &) ) and finding an implicit converse sequence to match the two together - that is operator C::A().
It is somewhat misleading that they apply - what seems to be - redundant language in the text for other copy-initialization cases.
The extra language on an implicit conversion sequence - that you quote in the question - for the other copy-initialization cases of (17.6.2) directly refer to the following case:
class B {
operator A();
};
class A {
};
B b;
A a = b;
This isn't covered under the first point in (17.6.1) as B doesn't derive from A nor do any helpful constructors exist. Therefore it falls under the 'other' clause.
I don't understand your concern, but answering what happens, in case this can solve it for you. A a{c} directly branches off of bullet 1 of dcl.initp17 to dcl.init.list.
From there it happens that it will initialize a const A& from a C (by over.match.list selecting the copy-constructor of A). This will invoke the paragraph you cited (by first branching off to dcl.init.ref and then applying dcl.initp17 in turn). This will construct a normal user-defined conversion sequence which is able to use conversion functions, such as your operator A.
I think you are confused by the fact that it only mentions user defined conversion sequences for the copy initializations, but in the preceeding bullet it doesn't. The reason is that when doing direct initialization, that is conceptual a direct call to the constructors of a class instead of being a conversion (in which the call to constructors is only a part, surrounded by a standard conversion sequence before (for constructors) and after (for conversion functions/operators) it). In other words, for direct initialization, you already force the constructor call, rather than making the compiler find out that it needs to do it. And you haven't "paid" a user defined conversion sequence (you cannot nest user defined conversion sequences, so they are "precious") - the compiler can still apply a user defined conversion sequence for matching up the argument for the constructor.
The proper testcase for this would be A a(c) rather than A a{c} though, to prevent branching off to dcl.init.list.

Overload resolution difference between gcc and clang involving move constructor and 'Derived(Base&&)' constructor

GCC (tested with 4.9) accepts the following testcase:
struct Base {};
struct Derived : Base {
Derived();
explicit Derived(const Derived&);
explicit Derived(Derived&&);
explicit Derived(const Base&);
Derived(Base&&);
};
Derived foo() {
Derived result;
return result;
}
int main() {
Derived result = foo();
}
Clang (tested with 3.5) rejects it with the following error message:
test.cpp:13:10: error: no matching constructor for initialization of 'Derived'
return result;
^~~~~~
test.cpp:8:5: note: candidate constructor not viable: no known conversion from 'Derived' to 'Base &&' for 1st argument
Derived(Base&&);
^
test.cpp:4:5: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
Derived();
^
Who is right?
I believe Clang is correct here. GCC should not be accepting the code.
The reason is the way overload resolution for constructors for the object copy occurring in a return statement is specified in [class.copy] p32 (emphasis mine):
When the criteria for elision of a copy/move constructor are met,
[...], and the object to be copied is designated by an lvalue, [...],
overload resolution to select the constructor for the copy is first
performed as if the object were designated by an rvalue. If the first
overload resolution fails or was not performed, or if the type of
the first parameter of the selected constructor is not an rvalue
reference to the object's type (possibly cv-qualified), overload
resolution is performed again, considering the object as an lvalue.
In this example, the criteria for elision are met (by the first bullet in [class.copy] p31) and the object to be copied is designated by an lvalue, so this paragraph applies.
Overload resolution is first attempted as if the object were designated by an rvalue. The explicit constructors are not candidates (see below for an explanation of why), so the Derived(Base&&) constructor is selected. However, this falls under "the type of the first parameter of the selected constructor is not an rvalue reference to the object's type" (instead, it's an rvalue reference to the type of the object's base class), so overload resolution should be performed again, considering the object as an lvalue.
This second overload resolution fails, because the only viable constructor (again, the explicit constructors are not candidates) has an rvalue reference parameter, which cannot bind to the lvalue. Clang shows the resulting overload resolution failure error.
To complete the explanation, here's why explicit constructors are not candidates for either overload resolution (all emphasis is mine).
First, [dcl.init] p15 says that:
The initialization that occurs in the = form of a
brace-or-equal-initializer or condition (6.4), 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."
Next, we look at [over.match.ctor] p1:
For copy-initialization, the candidate functions are all the converting
constructors (12.3.1) of that class.
Finally, we see that explicit constructors are not converting constructors in [class.conv.ctor] p1:
A constructor declared without the function-specifier
explicit specifies a conversion from the types of its parameters to the type of its class. Such a constructor is called a converting
constructor.

In copy-initialization, is the call to the copy constructor explicit or implicit?

class AAA {
public:
explicit AAA(const AAA&) {}
AAA(int) {}
};
int main() {
AAA a = 1;
return 0;
}
In the above code, as I understand, though elided in most cases, the copy constructor is still semantically required to be called. My question is, is the call explicit or implicit? For a long time I have the conclusion in my mind that the call to AAA::AAA(int) is implicit but the call to the copy constructor is not. Today I accidentally got g++ to compile the above code and it reported error. (VC12 compiles OK.)
In section 8.5 of the standard:
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.
The bolded direct-initialize in the above quotes means the call to copy constructor is explicit, right? Is g++ wrong or my interpretation of the standard wrong?
Looks like this bug: g++ fails to call explicit constructors in the second step of copy initialization
g++ fails to compile the following code
struct X
{
X(int) {}
explicit X(X const &) {}
};
int main()
{
X x = 1; // error: no matching function for call to 'X::X(X)'
}
The second step of a copy initialization (see 8.5/16/6/2) is a
direct-initialization where explicit constructors shall be considered
as candidate functions.
Looks like copy constructor is never called . Only constructor is called . The following code may call copy constructor
AAA a = 1;
AAA ab = a;
Not sure why G++ is compiling it .

Is it possible to invoke a user-defined conversion function via list-initialization?

Is this program legal?
struct X { X(const X &); };
struct Y { operator X() const; };
int main() {
X{Y{}}; // ?? error
}
After n2672, and as amended by defect 978, 13.3.3.1 [over.best.ics] has:
4 - However, when considering the argument of a constructor or user-defined conversion function that is a candidate [...] by 13.3.1.7 [...] when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X [...], only standard conversion sequences and ellipsis conversion sequences are considered.
This seems rather perverse; it has the result that specifying a conversion using a list-initialization cast is illegal:
void f(X);
f(Y{}); // OK
f(X{Y{}}); // ?? error
As I understand n2640, list-initialization is supposed to be able to replace all uses of direct-initialization and copy-initialization, but there seems no way to construct an object of type X from an object of type Y using only list-initialization:
X x1(Y{}); // OK
X x2 = Y{}; // OK
X x3{Y{}}; // ?? error
Is this the actual intent of the standard; if not, how should it read or be read?
The original intent of 13.3.3.1p4 is to describe how to apply the requirement in 12.3p4 that:
4 - At most one user-defined conversion (constructor or conversion function) is implicitly applied to a single value.
Before defect 84, 13.3.3.1p4 was almost purely informative:
4 - In the context of an initialization by user-defined conversion (i.e., when considering the argument of a user-defined conversion function; see 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv]), only standard conversion sequences and ellipsis conversion sequences are allowed.
This is because 13.3.1.4 paragraph 1 bullet 2 and 13.3.1.5p1b1 restrict the candidate functions to those on class S yielding type T, where S is the class type of the initializer expression and T is the type of the object being initialized, so there is no latitude for another user-defined conversion conversion sequence to be inserted. (13.3.1.4p1b1 is another matter; see below).
Defect 84 repaired the auto_ptr loophole (i.e. auto_ptr<Derived> -> auto_ptr<Base> -> auto_ptr_ref<Base> -> auto_ptr<Base>, via two conversion functions and a converting constructor) by restricting the conversion sequences allowable for the single parameter of the constructor in the second step of class copy-initialization (here the constructor of auto_ptr<Base> taking auto_ptr_ref<Base>, disallowing the use of a conversion function to convert its argument from auto_ptr<Base>):
4 - However, when considering the argument of a user-defined conversion function that is a candidate by 13.3.1.3 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, or by 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed.
n2672 then adds:
[...] by 13.3.1.7 [over.match.list] when passing the initializer list as a single argument or when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X, [...]
This is clearly confused, as the only conversions that are a candidate by 13.3.1.3 and 13.3.1.7 are constructors, not conversion functions. Defect 978 corrects this:
4 - However, when considering the argument of a constructor or user-defined conversion function [...]
This also makes 13.3.1.4p1b1 consistent with 12.3p4, as it otherwise would allow unlimited application of converting constructors in copy-initialization:
struct S { S(int); };
struct T { T(S); };
void f(T);
f(0); // copy-construct T by (convert int to S); error by 12.3p4
The issue is then what the language referring to 13.3.1.7 means. X is being copy or move constructed so the language is excluding applying a user-defined conversion to arrive at its X argument. std::initializer_list has no conversion functions so the language must be intended to apply to something else; if it isn't intended to exclude conversion functions, it must exclude converting constructors:
struct R {};
struct S { S(R); };
struct T { T(const T &); T(S); };
void f(T);
void g(R r) {
f({r});
}
There are two available constructors for the list-initialization; T::T(const T &) and T::T(S). By excluding the copy constructor from consideration (as its argument would need to be converted via a user-defined conversion sequence) we ensure that only the correct T::T(S) constructor is considered. In the absence of this language the list-initialization would be ambiguous. Passing the initializer list as a single argument works similarly:
struct U { U(std::initializer_list<int>); };
struct V { V(const V &); V(U); };
void h(V);
h({{1, 2, 3}});
Edit: and having gone through all that, I've found a discussion by Johannes Schaub that confirms this analysis:
This is intended to factor out the copy constructor for list initialization
because since we are allowed to use nested user defined conversions, we
could always produce an ambiguous second conversion path by first invoking
the copy constructor and then doing the same as we did for the other
conversions.
OK, off to submit a defect report. I'm going to propose splitting up 13.3.3.1p4:
4 - However, when considering the argument of a constructor or user-defined conversion function that is a candidate:
by 13.3.1.3 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, or
by 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] in all cases,
only standard conversion sequences and ellipsis conversion sequences are considered; when considering the first argument of a constructor of a class X that is a candidate by 13.3.1.7 [over.match.list] when passing the initializer list as a single argument or when the initializer list has exactly one element, a user-defined conversion to X or reference to (possibly cv-qualified) X is only considered if its user-defined conversion is specified by a conversion function. [Note: because more than one user-defined conversion is allowed in an implicit conversion sequence in the context of list-initialization, this restriction is necessary to ensure that a converting constructor of X, called with a single argument a that is not of type X or a type derived from X, is not ambiguous against a constructor of X called with a temporary X object itself constructed from a. -- end note]
The version of clang 3.1 shipped with XCode 4.4 agrees with your interpretation and rejects X{Y{}};. As do I, after re-reading the relevant parts of the standard a few times, FWIW.
If I modify X's constructor to take two arguments, both of type const X&, clang accepts the statement Y y; X{y,y}. (It crashes if I try X{Y{},Y{}}...). This seems to be consistent with 13.3.3.1p4 which demands user-defined conversions to be skipped only for the single-element case.
It seems that the restriction to standard and ellipsis conversion sequences was added initially only in cases where another user-defined conversion has already taken place. Or at least that is how I read http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#84.
It's interesting how the standard is careful to apply the restriction only to the second step of copy initialization, which copies from a temporary which already has the correct type (and was obtain potentially through a user-defined conversion!). Yet for list-initialization, no similar mechanism seems to exists...