Copy list initialisation and explicit constructor allowed? - c++

I am using the following code in VS2013 and it compiles.
explicit QIcon(const QString &fileName); // file or resource name
void setWindowIcon(const QIcon &icon);
I call the function like this:
setWindowIcon({ "icon.png" });
However in Clang 3.7.1 it fails with:
error chosen constructor is explicit in copy-initialization
I read in other questions that in the C++ standard, §13.3.1.7 [over.match.list], the following is stated:
In copy-list-initialization, if an explicit constructor is chosen, the
initialization is ill-formed.
Is VS2013 wrong in allowing this code to compile?

Yes, VS2013 is wrong in allowing the code to compile.
The important rule is in [over.ics.list] (quote from N3337):
[over.ics.list]/1]: When an argument is an initializer list (8.5.4), it is not an expression and special rules apply for converting
it to a parameter type.
[over.ics.list]/3]: Otherwise, if the parameter is a non-aggregate class X and overload resolution per 13.3.1.7 chooses a single
best constructor of X to perform the initialization of an object of type X from the argument initializer list, the
implicit conversion sequence is a user-defined conversion sequence. If multiple constructors are viable but
none is better than the others, the implicit conversion sequence is the ambiguous conversion sequence. User-defined conversions are allowed for conversion of the initializer list elements to the constructor parameter
types except as noted in 13.3.3.1.
13.3.3.1 outlines implicit conversion sequences, which references [class.conv.ctor] regarding user-defined conversions:
[class.conv.ctor]/1: 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.
So the constructor must not be marked explicit if it should be used for this form of initialization.

Related

Ambiguous function call in msvc and clang but not in gcc

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.

Is the copy-list-initialization for class object a user-defined conversion

#include <iostream>
struct Data{
Data(int){
}
}
int main(){
Data d = {0}; //#1
}
As the above code show,Does the #1 invocation contain a user-defined conversion?In my understanding about the standard ,I think it does not
For copy-list-initialization rules [dcl.init.list]
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
[over.match.list]
If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list
The standard only said the best match constructor is used to initialize the object which is initialized by using the element of initializer list,it is different with copy-initialization(the copy-initialization say that "user-defined conversion sequences that can convert from the source type to the destination type",explicit define the copy-initialization need a user-defined conversion)
So Data d = {0}; => Data d(0); there's no user-defined conversion other than standard conversions?Is my understanding right?
However another terms [class.conv]
Type conversions of class objects can be specified by constructors and by conversion functions. These conversions are called user-defined conversions and are used for implicit type conversions (Clause [conv]), for initialization, and for explicit type conversions
the above term means if the initialized destination type is class type and need to use constructors or conversion functions,then the conversions are "user-defined conversions"
I'm confused by these terms,what actually the Data d = {0}; is a user-defined conversion or not?
Type conversions of class objects can be specified by constructors and by conversion functions.
User-defined conversion is first a type conversion. In the initialization Data d = {0};, there is even no type conversion, thus no user-defined conversion.

Double brace initialization

Which constructor should be called in the following code and why?
struct S
{
int i;
S() = default;
S(void *) : i{1} { ; }
};
S s{{}};
If I use clang (from trunk), then the second one is called.
If the second constructor is commented out, then S{{}} is still valid expression, but (I believe) move-constructor from default-constructed instance of S{} is called in the case.
Why conversion constructor has priority over the default one in the very first case?
The intention of such a combination of the constructors of S is to save its std::is_trivially_default_constructible_v< S > property, except a finite set of cases, when it should be initialized in a certain way.
If the second constructor is commented out, then S{{}} is still valid expression, but (I sure) move-constructor from default-constructed instance of S{} is called in the case.
Actually, that's not what happens. The ordering in [dcl.init.list] is:
List-initialization of an object or reference of type T is defined as follows:
— If T is an aggregate class and the initializer list has a single element of type cv U, [...]
— Otherwise, if T is a character array and [...]
— Otherwise, if T is an aggregate, aggregate initialization is performed (8.6.1).
Once you remove the S(void *) constructor, S becomes an aggregate - it has no user-provided constructor. S() = default doesn't count as user-provided because reasons. Aggregate initialization from {} will end up value-initializing the i member.
Why conversion constructor has priority over the default one in the very first case?
With the void* remaining, let's keep going down the bullet list:
— Otherwise, if the initializer list has no elements [...]
— Otherwise, if T is a specialization of std::initializer_list, [...]
— 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).
[over.match.list] gives us a two-phase overload resolution process:
— Initially, the candidate functions are the initializer-list constructors (8.6.4) of the class T and the
argument list consists of the initializer list as a single argument.
— If no viable initializer-list constructor is found, overload resolution is performed again, where the
candidate functions are all the constructors of the class T and the argument list consists of the elements
of the initializer list.
If the initializer list has no elements and T has a default constructor, the first phase is omitted.
S doesn't have any initializer list constructors, so we go into the second bullet and enumerate all the constructors with the argument list of {}. We have multiple viable constructors:
S(S const& );
S(S&& );
S(void *);
The conversion sequences are defined in [over.ics.list]:
Otherwise, if the parameter is a non-aggregate class X and overload resolution per 13.3.1.7 chooses a single
best constructor C of X to perform the initialization of an object of type X from the argument initializer list:
— If C is not an initializer-list constructor and the initializer list has a single element of type cv U, [...]
— Otherwise, the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion.
and
Otherwise, if the parameter type is not a class: [...] — if the initializer list has no elements, the implicit conversion sequence is the identity conversion.
That is, the S(S&& ) and S(S const& ) constructors are both user-defined conversion sequences plus identity conversion. But S(void *) is just an identity conversion.
But, [over.best.ics] has this extra rule:
However, if the target is
— the first parameter of a constructor or
— the implicit object parameter of a user-defined conversion function
and the constructor or user-defined conversion function is a candidate by
— 13.3.1.3, when [...]
— 13.3.1.4, 13.3.1.5, or 13.3.1.6 (in all cases), or
— the second phase of 13.3.1.7 when the initializer list has exactly one element that is itself an initializer list, and the target is the first parameter of a constructor of class X, and the conversion is to X or reference to (possibly cv-qualified) X,
user-defined conversion sequences are not considered.
This excludes from consideration S(S const&) and S(S&& ) as candidates - they are precisely this case - the target being the first parameter of the constructor as a result of the second phase of [over.match.list] and the target being a reference to possibly cv-qualified S, and such a conversion sequence would be user-defined.
Hence, the only remaining candidate is S(void *), so it's trivially the best viable candidate.

Why does C++ allow std::initializer_list to be coerced to primitive types, and be used to initialise them?

This question is regarding std::initializer_list, and why it is allowed to initialise primitive types. Consider the following two functions:
void foo(std::string arg1, bool arg2 = false);
void foo(std::string arg1, std::deque<std::string> arg2, bool arg3 = false);
Why is it that, when calling foo like this:
foo("some string", { });
The first overload is picked, instead of the second? Well, actually not why it's picked, it's because { } can be used to initialise anything, including primitive types. My question is the reasoning behind this.
std::initializer_list takes { args... }, and as such cannot have indeterminate length at the time of compilation. Attempting to do something like bool b = { true, true } gives error: scalar object 'b' requires one element in initialiser.
While it might have seemed like a good idea to allow uniform initialisation, the fact is that this is confusing and entirely unexpected behaviour. Indeed, how is the compiler able to do this, without some magic in the background doing std::initializer_list things?
Unless { args... } is a C++ lexical construct, in which case my point still stands: why is it allowed to be used in the initialisation of primitive types?
Thanks. I had quite the bug-hunting session here, before realising that the wrong overload was being called. Spent 10 minutes figuring out why.
That {} syntax is a braced-init-list, and since it is used as an argument in a function call, it copy-list-initializes a corresponding parameter.
§ 8.5 [dcl.init]/p17:
(17.1) — If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized (8.5.4).
§ 8.5.4 [dcl.init.list]/p1:
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; [...]
For a class-type parameter, with list-initialization, overload resolution looks up for a viable constructor in two phases:
§ 13.3.1.7 [over.match.list]/p1:
When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor
in two phases:
— Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
— If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.
but:
If the initializer list has no elements and T has a default constructor, the first phase is omitted.
Since std::deque<T> defines a non-explicit default constructor, one is added to a set of viable functions for overload resolution. Initialization through a constructor is classified as a user-defined conversion (§ 13.3.3.1.5 [over.ics.list]/p4):
Otherwise, if the parameter is a non-aggregate class X and overload resolution per 13.3.1.7 chooses a single
best constructor of X to perform the initialization of an object of type X from the argument initializer list,
the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion
sequence an identity conversion.
Going further, an empty braced-init-list can value-initialize its corresponding parameter (§ 8.5.4 [dcl.init.list]/p3), which for literal types stands for zero-initialization:
(3.7) — Otherwise, if the initializer list has no elements, the object is value-initialized.
This, for literal types like bool, doesn't require any conversion and is classified as a standard conversion (§ 13.3.3.1.5 [over.ics.list]/p7):
Otherwise, if the parameter type is not a class:
(7.2) — if the initializer list has no elements, the implicit conversion sequence is the identity conversion.
[ Example:
void f(int);
f( { } );
// OK: identity conversion
— end example ]
Overload resolution checks in first place if there exists an argument for which a conversion sequence to a corresponding parameter is better than in another overload (§ 13.3.3 [over.match.best]/p1):
[...] Given these definitions, a viable function F1 is defined to be a better function than another viable function
F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then:
(1.3) — for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that, [...]
Conversion sequences are ranked as per § 13.3.3.2 [over.ics.rank]/p2:
When comparing the basic forms of implicit conversion sequences (as defined in 13.3.3.1)
(2.1) — a standard conversion sequence (13.3.3.1.1) is a better conversion sequence than a user-defined conversion sequence or an ellipsis conversion sequence, and [...]
As such, the first overload with bool initialized with {} is considered as a better match.
Unfortunately, {} does not actually indicate an std::initializer_list. It is also used for uniform initialization. Uniform initialization was intended to fix the problems of the piles of different ways C++ objects could be initialized but ended up just making things worse, and the syntactic conflict with std::initializer_list is fairly awful.
Bottom line is that {} to denote an std::initializer_list and {} to denote uniform initialization are two different things, except when they're not.
Indeed, how is the compiler able to do this, without some magic in the
background doing std::initialiser_list things?
The aforementioned magic most assuredly exists. { args... } is simply a lexical construct and the semantic interpretation depends on context- it is certainly not an std::initializer_list, unless the context says it is.
why is it allowed to be used in the initialisation of primitive types?
Because the Standards Committee did not properly consider how broken it was to use the same syntax for both features.
Ultimately, uniform init is broken by design, and should realistically be banned.
My question is the reasoning behind this.
The reasoning behind it is simple (albeit flawed). List-initialization initializes everything.
In particular, {} stands for "default" initializing the object it corresponds to; Whether this means that its initializer_list-constructor is called with an empty list, or that its default constructor is called, or that it is value-initialized, or that all of an aggregates subobjects are initialized with {}, etc. is irrelevant: It is supposed to act as a universal initializer for any object that the above can be applied to.
If you wanted to call the second overload, you'd have to pass e.g. std::deque<std::string>{} (or pass three arguments in the first place). That is the current modus operandi.
While it might have seemed like a good idea to allow uniform
initialisation, the fact is that this is confusing and entirely
unexpected behaviour.
I wouldn't call it "entirely unexpected" by any means. What is confusing about list-initializing primitive types? It is absolutely vital for aggregates - but there's not that big of a step from aggregate types to arithmetic ones, as no initializer_list is involved in both cases. Don't forget that it can e.g. be useful to prevent narrowing as well.
std::initialiser_list takes { args... }, and as such cannot have
indeterminate length at the time of compilation.
Well, technically speaking,
std::initializer_list<int> f(bool b) {
return b? std::initializer_list<int>{} : std::initializer_list<int>{1};
}

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