I have two classes, one of which, say, represents a string, and the other can be converted to a string:
class A {
public:
A() {}
A(const A&) {}
A(const char*) {}
A& operator=(const A&) { return *this; }
A& operator=(const char*) { return *this; }
char* c;
};
class B {
public:
operator const A&() const {
return a;
}
operator const char*() const {
return a.c;
}
A a;
};
Now, if I do
B x;
A y = x;
It triggers copy constructor, which compiles fine. But if I do
A y;
y = x;
It complains about ambiguous assignment, and can't choose between =(A&) and =(char*). Why the difference?
There is a difference between initialization and assignment.
In initialization, that is:
A y = x;
The actual call depends on the type of x. If it is the same type of y, then it will be like:
A y(x);
If not, as in your example, it will be like:
A y(static_cast<const A&>(x));
And that compiles fine, because there is no ambiguity any more.
In the assignment there is no such special case, so no automatic resolution of the ambiguity.
It is worth noting that:
A y(x);
is also ambiguous in your code.
There is §13.3.1.4/(1.2), only appertaining to (copy-)initialization of objects of class type, that specifies how candidate conversion functions for your first case are found:
Under the conditions specified in 8.5, as part of a
copy-initialization of an object of class type, a user-defined
conversion can be invoked to convert an initializer expression to the
type of the object being initialized. Overload resolution is used to
select the user-defined conversion to be invoked. […] Assuming that
“cv1 T” is the type of the object being initialized, with T a class
type, the candidate functions are selected as follows:
The converting constructors (12.3.1) of T are candidate
functions.
When the type of the initializer expression is a class type
“cv S”, the non-explicit conversion functions of S and its base
classes are considered. When initializing a temporary to be bound to
the first parameter of a constructor where the parameter is of type
“reference to possibly cv-qualified T” and the constructor is called
with a single argument in the context of direct-initialization of an
object of type “cv2 T”, explicit conversion functions are also
considered. Those that are not hidden within S and yield a type
whose cv-unqualified version is the same type as T or is a derived
class thereof are candidate functions. […] Conversion functions that return “reference to X” return lvalues or xvalues,
depending on the type of reference, of type X and are therefore considered to yield X for this process of selecting candidate functions.
I.e. operator const char* is, though being considered, not included in the candidate set, since const char* is clearly not similar to A in any respect. However, in your second snippet, operator= is called as an ordinary member function, which is why this restriction doesn't apply anymore; Once both conversion functions are in the candidate set, overload resolution will clearly result in an ambiguity.
Note that for direct-initialization, the above rule doesn't apply either.
B x;
A y(x);
Is ill-formed.
A more general form of this result is that there can never be two user-defined conversions in one conversion sequence during overload resolution. Consider §13.3.3.1/4:
However, if the target is
the first parameter of a constructor or […]
and the constructor […] is a candidate
by
13.3.1.3, when the argument is the temporary in the second step of a class copy-initialization, or
13.3.1.4, 13.3.1.5, or 13.3.1.6 (in all cases),
user-defined conversion sequences are not considered. [Note: These
rules prevent more than one user-defined conversion from being applied
during overload resolution, thereby avoiding infinite recursion. — end
note ]
Related
A class can declare several conversion operators. In particular it can be conversion operators to some type and to const-reference of the same type. Which of the two conversion operators must be selected in case of requested conversion to that type?
Consider an example:
#include <iostream>
struct B {};
static B sb;
struct A {
operator B() { std::cout << "operator B() "; return sb; }
operator const B &() { std::cout << "operator const B &() "; return sb; }
};
int main() {
A a;
[[maybe_unused]] B b(a);
}
Here Clang selects operator B(), MSVC selects operator const B &(), and GCC complains about ambiguity of the selection:
<source>:13:27: error: call of overloaded 'B(A&)' is ambiguous
13 | [[maybe_unused]] B b(a);
| ^
<source>:3:8: note: candidate: 'constexpr B::B(const B&)'
3 | struct B {};
| ^
<source>:3:8: note: candidate: 'constexpr B::B(B&&)'
Demo: https://gcc.godbolt.org/z/874h7h3d1
Which of the compilers is right?
The program is ill-formed and rejected by GCC is correct here but the diagnosis can arguably say it is not completely correct. For this declaration B b(a);, it is direct-initialization of an object of class B from the initializer a of type A, according to [over.match.copy] p1
Assuming that “cv1 T” is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:
The converting constructors of T are candidate functions.
When the type of the initializer expression is a class type “cv S”, conversion functions are considered. The permissible types for non-explicit conversion functions are T and any class derived from T. When initializing a temporary object ([class.mem]) to be bound to the first parameter of a constructor where the parameter is of type “reference to cv2 T” and the constructor is called with a single argument in the context of direct-initialization of an object of type “cv3 T”, the permissible types for explicit conversion functions are the same; otherwise there are none.
For converting constructors, they are copy/move constructors of B, however, [over.best.ics#general-4] prohibits the user-defined conversion sequence to apply to the target to match the parameter of the constructor
However, if the target is
the first parameter of a constructor or
[...]
and the constructor or user-defined conversion function is a candidate by
[...]
[over.match.copy], [over.match.conv], or [over.match.ref] (in all cases), or
[...]
user-defined conversion sequences are not considered.
Hence, the copy/move constructors of B are not viable functions. The ambiguity arises from the viable functions A::operator B() and A::operator const B &(), since the implicit parameter objects of them both have type A& and the corresponding argument is an lvalue of type A, hence neither is better than the other. Hence, the only opportunity that can determine which is better falls on [over.match.best#general-2.2]
the context is an initialization by user-defined conversion (see [dcl.init], [over.match.conv], and [over.match.ref]) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type.
The second standard conversion sequences of them are both identity conversions, hence they are not indistinguishable. So, the result is ambiguity. GCC is merely correct in that the program is ambiguous, but, obviously, its diagnosis has a bit misleading. Since the copy/move constructors are not viable functions in this case at all, how could they cause the ambiguity? If we suppress the production of the defaulted move constructor, GCC and Clang are both incorrect here, which is back to this question you have referred.
The piece of code below dereferences a nullptr.
struct Foo {
int *bar;
operator int&() {
return *bar;
}
explicit operator bool() const {
return bar;
}
};
int main() {
Foo f {nullptr};
auto _ = bool(f);
}
Why does bool(f) call bool(f.operator int&()) and not f.operator bool() as was intended?
Is there a way to make bool(f) call f.operator bool() as intended without marking operator int& as explicit?
Argument conversion ranking takes precedence over return type conversion ranking in overload resolution for user-defined conversions
All standard references below refers to N4659: March 2017 post-Kona working draft/C++17 DIS.
Preparations
To avoid having to deal with segfaults, auto type deduction and consideration of explicit marked conversion functions, consider the following simplified variation of your example, which shows the same behaviour:
#include <iostream>
struct Foo {
int bar{42};
operator int&() {
std::cout << __PRETTY_FUNCTION__;
return bar;
}
operator bool() const {
std::cout << __PRETTY_FUNCTION__;
return true;
}
};
int main() {
Foo f {};
// (A):
bool a = f; // Foo::operator int&()
}
TLDR
Why does bool(f) call bool(f.operator int&()) and not f.operator bool() as was intended?
The viable candidate functions to the initialization conversion sequence are
operator int&(Foo&)
operator bool(const Foo&)
and [over.match.best]/1.3, ranking on function arguments, takes precedence over [over.match.best]/1.4, ranking on conversion from return type, meaning operator int&(Foo&) will be chosen as it is a non-ambiguous perfect match, for an argument of type Foo&, by the [over.match.best]/1.3 rule, whereas operator bool(const Foo&) is not.
In this regard, as we are relying on [over.match.best]/1.3, it is no different than simply overloading solely on const-qualification:
#include <iostream>
struct Foo {};
void f(Foo&) { std::cout << __PRETTY_FUNCTION__ << "\n"; }
void f(const Foo&) { std::cout << __PRETTY_FUNCTION__ << "\n"; }
int main() {
Foo f1 {};
const Foo f2{};
f(f1); // void f(Foo&)
f(f2); // void f(const Foo&)
}
Is there a way to make bool(f) call f.operator bool() as intended without marking operator int& as explicit?
As per above, if you provide matching cv-qualifiers for the member function overloads, there can no longer be any differentiation on the implicit object argument as per [over.match.best]/1.3, and the bool conversion function will be chosen as most viable as per [over.match.best]/1.4. Note that by marking the int& conversion functions as explicit, it will no longer be a viable candidate, and the choice for the bool conversion function will not be due to it being the most viable overload, but by it being the only viable overload.
Details
The expression at (A) is initialization, with semantics governed specifically by [dcl.init]/17.7 [extract, emphasis mine]:
[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.
[...]
/17.7 Otherwise, if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable
conversion functions are enumerated ([over.match.conv]), and the best
one is chosen through overload resolution. The user-defined conversion
so selected is called to convert the initializer expression into the
object being initialized. [...]
where [over.match.conv]/1 describes which conversion functions that are considered candidate functions in overload resolution:
[over.match.conv]/1 Under the conditions specified in [dcl.init], as part of an initialization of an object of non-class type, a conversion function can be invoked to convert an initializer expression of class type to the type of the object being initialized. Overload resolution is used to select the conversion function to be invoked. Assuming that “cv1 T” is the type of the object being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:
/1.1 The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not
hidden within S and yield type T or a type that can be converted
to type T via a standard conversion sequence are candidate
functions. [...] Conversion functions that return “reference to cv2
X” return lvalues or xvalues, depending on the type of reference, of
type “cv2 X” and are therefore considered to yield X for this
process of selecting candidate functions.
In this example, cv T, the type of the object being initialized, is bool, and thus both used-defined conversion functions are viable candidates, as one directly yields type bool and the other yields a type that can be converted to type bool via a standard conversion sequence (int to bool); as per [conv.bool]:
A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a prvalue of type bool. [...]
Moreover, the type of the initializer expression is Foo, and [over.match.funcs]/4 governs that the cv-qualification of the type of the implicit object parameter for user-defined conversion functions is that of the cv-qualification of the respective function:
For non-static member functions, the type of the implicit object
parameter is
“lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
[...]
where X is the class of which the function is a member and cv is
the cv-qualification on the member function declaration. [...] For
conversion functions, the function is considered to be a member of the
class of the implied object argument for the purpose of defining the
type of the implicit object parameter. [...]
Thus, w.r.t. overload resolution we may summarize as follows:
// Target type: bool
// Source type: Foo
// Viable candidate functions:
operator int&(Foo&)
operator bool(const Foo&)
where we have added the implied object parameter as explicit function parameter, without loss of generality (as per [over.match.funcs]/5), when continuing for how overload resolution picks the best viable candidate.
Now, [over.ics.user], particularly [over.ics.user]/2 summarizes this:
[over.ics.user]/2 [...] Since an implicit conversion sequence is an initialization, the special rules for initialization by user-defined conversion apply when selecting the best user-defined conversion for a user-defined conversion sequence (see [over.match.best] and [over.best.ics]).
particularly that the rules for selecting the best viable candidate is governed by [over.match.best], particularly [over.match.best]/1:
[over.match.best]/1 [...] 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,
/1.4 the context is an initialization by user-defined conversion (see [dcl.init], [over.match.conv], and [over.match.ref])
and the standard conversion sequence from the return type of F1 to
the destination type (i.e., the type of the entity being
initialized) is a better conversion sequence than the standard
conversion sequence from the return type of F2 to the destination
type
The key here is that [over.match.best]/1.4, regarding conversion on the return type of the candidate (to the target type) only applies if the overloads cannot be disambiguated by means of [over.match.best]/1.3. In our example, however, recall that the viable candidate functions are:
operator int&(Foo&)
operator bool(const Foo&)
As per [over.ics.rank]/3.2, particularly [over.ics.rank]/3.2.6:
[over.ics.rank]/3 Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the
following rules applies:
[...]
/3.2 Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if
[...]
/3.2.6 S1 and S2 are reference bindings ([dcl.init.ref]), and the types to which the references refer are the same type
except for top-level cv-qualifiers, and the type to which the
reference initialized by S2 refers is more cv-qualified than the
type to which the reference initialized by S1 refers.
meaning, that for an argument of type Foo&, operator int&(Foo&) will be a better (/1.3: exact) match, whereas for e.g. an argument of type const Foo&, operator bool(const Foo&) will the only match (Foo& will not be viable).
#include <iostream>
using namespace std;
struct CL2
{
CL2(){}
CL2(const CL2&){}
};
CL2 cl2;
struct CL1
{
CL1(){}
operator CL2&(){cout<<"operator CL2&"; return cl2;}
operator const CL2&(){cout<<"operator const CL2&"; return cl2;}
};
CL1 cl1;
int main()
{
CL1 cl1;
CL2 cl2 (cl1);
}
Both clang and gcc give ambiguous conversion operator, but Visual Studio compiles ok and prints "operator const CL2&". How must be right according to Standard?
As I undestand, conversion of CL1 to const CL2& is in copy-initialization context (as a part of a direct-initialization of cl2 object). I seen n4296 draft, [over.match.copy]:
Assuming that “cv1 T” is the type of the object being initialized,
with T a class type, the candidate functions are selected as follows:
— The converting constructors (12.3.1) of T are candidate
functions.
— When the type of the initializer expression is a
class type “cv S”, the non-explicit conversion functions of S and its
base classes are considered. When initializing a temporary to be bound
to the first parameter of a constructor where the parameter is of type
“reference to possibly cv-qualified T” and the constructor is called
with a single argument in the context of direct-initialization of an
object of type “cv2 T”, explicit conversion functions are also
considered. Those that are not hidden within S and yield a type whose
cv-unqualified version is the same type as T or is a derived class
thereof are candidate functions. Conversion functions that return
“reference to X” return lvalues or xvalues, depending on the type of
reference, of type X and are therefore considered to yield X for this
process of selecting candidate functions.
I.e. both of conversion operators are considered as return CL2 and const CL2 (not just CL2 without const) and it remains to solve, which conversion is better: CL2 -> const CL2& or const CL2 -> const CL2&. The second case seems more appropriate. Should a better qualification conversion considered in that context? Or both cases are Identity conversion? I couldn't find it in Standard
Since both conversion operators have identical signatures, the only way in which one could be preferred over the other is by application of [over.match.best]/(1.4)…
— the context is an initialization by user-defined conversion (see 8.5,
13.3.1.5, and 13.3.1.6) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the
entity being initialized) is a better conversion sequence than the
standard conversion sequence from the return type of F2 to the
destination type.
…or (1.5):
— the context is an initialization by conversion function for direct
reference binding (13.3.1.6) of a reference to function type, […]
Clearly, neither applies, hence the ambiguity. A possible way to disambiguate:
operator CL2&();
operator const CL2&() const;
Demo; Here, the former overload's initial standard conversion sequence of the implicit object argument is better as per [over.ics.rank]/(3.2.6), which is decisive by [over.match.best]/(1.3).
I have the following code snippet:
struct T {
T(const T&) = default;
T(const S &);
};
struct S {
operator T();
};
int main() {
S s;
T t = s; // copy-initialization of class type
return 0;
}
My question is why the compiler prefers S::operator T() for the initialization of t rather than reporting an error that the initialization is ambigious. In my opinion the following happens (correct me if i am wrong):
t is copy-initialized with an lvalue of type S
S is not T and S is also not a subclass of T, so S and T are unrelated
because of the fact that the variable t is copy-initialized and the fact that the types S and T are unrelated, the compiler tries to find user-defined-conversion sequences to do the initialization.
overload resolution is responsible for selecting the best user-defined-conversion which can be either a converting constructor of T or the conversion function of S
the implicite conversion sequence for the constructor T::T(const S&) from the argument s is the identity conversion because the lvalue s can be bound directly to this lvalue reference
the implicite conversion sequence for the conversion function S::operator T() from the argument s is also the identity conversion, because the implicit object parameter is S&
Both the constructor and the conversion function return a prvalue of type T which can be used to direct-initialize the variable t. That means that the second standard conversion sequence of both user-defined-conversion sequences is the identity conversion.
This would mean that both user-defined-conversion sequences are equally good. Or is there a special rule which prefers the conversion functions?
I was reading the following rules in the c++11 standard:
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.
The semantics of initializers are as follows...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....
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)
User-defined conversion sequence U1 is a better conversion sequence than another user defined conversion sequence U2 if they contain the same user-defined conversion function or constructor and if the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2
Maybe i am making false assumptions. I hope you can help me!
The conversion from S using the conversion operator is better than the conversion to T taking an S const as argument. If you make s an S const, the constructor is preferred: Your identity operation in one case, indeed, is an identity operation, in the other case it isn't. If you make the conversion operator of S a const member, you get an ambiguity. Below is a test program demonstrating all cases:
struct S;
struct T {
T(const T&) = default;
T(const S &);
};
struct S {
S(); // needed to allow creation of a const object
#ifdef AMBIGUOUS
operator T() const;
#else
operator T();
#endif
};
int main() {
#ifdef CONST
S const s;
#else
S s;
#endif
T t = s; // copy-initialization of class type
return 0;
}
I think i found the rule which clarifies this:
13.3.3.2: ... S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.
In the member function S::operator T() the implicit object parameter has type S& which is directly bound to the lvalue s of type S. In the constructor T::T(const S&) the parameter is directly bound to the lvalue s of type S but this reference binding is more cv-qualified than in the operator function, so the operator function is preferred by overload resolution.
Do you agree with this?
Post-standard draft n3376 has as an example (12.3.2:2) of the use of an explicit conversion function to a user-defined type:
class Y { };
struct Z {
explicit operator Y() const;
};
void h(Z z) {
Y y1(z); // OK: direct-initialization
}
Per 12.3.2:2, an explicit conversion function is "only considered as a user-defined conversion for direct-initialization"; however, that would appear to permit:
struct Y { Y(int); };
struct Z {
explicit operator int() const;
};
void h(Z z) {
Y y1(z); // direct-initialization
}
which appears to conflict with the intent of the standard, and indeed is rejected by gcc-4.7.1:
source.cpp: In function 'void h(Z)':
source.cpp:4:9: error: no matching function for call to 'Y::Y(Z&)'
source.cpp:4:9: note: candidates are:
source.cpp:1:12: note: Y::Y(int)
source.cpp:1:12: note: no known conversion for argument 1 from 'Z' to 'int'
source.cpp:1:8: note: constexpr Y::Y(const Y&)
source.cpp:1:8: note: no known conversion for argument 1 from 'Z' to 'const Y&'
source.cpp:1:8: note: constexpr Y::Y(Y&&)
source.cpp:1:8: note: no known conversion for argument 1 from 'Z' to 'Y&&'
Is gcc correct to reject the conversion from Z to Y via int, or does the standard indeed permit this usage?
I considered the context of the mentioned direct-initialization; per the definition of direct-initialization to class type in 8.5:16, a constructor is called with the initializer expression as its arguments, which therefore are converted to the parameter type by an implicit conversion sequence (13.3.3.1). Since an implicit conversion sequence is an implicit conversion (4:3), and thus models copy-initialization (8.5:14) and not direct-initialization, the language in 12.3.2:2 must be referring to the expression as a whole.
Note also that this isn't a violation of 12.3:4 (multiple user-defined conversions); the same compiler is happy with the same code with explicit removed (as are Clang and Comeau):
struct Y { Y(int); };
struct Z { operator int(); };
void h(Z z) {
Y y1(z); // direct-initialization
}
I think Jesse Good has identified the distinction between the operator Y and operator int cases in 13.3.1.4:1, but there's a third case that I'm still concerned by:
struct X {};
struct Y { Y(const X &); };
struct Z {
explicit operator X() const;
};
void h(Z z) {
Y y1(z); // direct-initialization via class-type X
}
The initialization of the temporary X to be bound to the single const X & parameter of the constructor of Y proceeds in a direct-initialization context per 13.3.1.4:1, with T as X and S as Z. I think this clause is incorrect and should read:
13.3.1.4 Copy-initialization of class by user-defined conversion [over.match.copy]
1 - [...] When initializing a temporary to be bound to the first parameter
of a constructor that takes a reference to possibly cv-qualified T as its first argument, called with a single argument in the context of direct-initialization of an object of type "cv2 T", explicit conversion functions are also considered. [...]
For the avoidance of confusion, I think 12.3.2:2 should also be amended:
12.3.2 Conversion functions [class.conv.fct]
2 - A conversion function may be explicit (7.1.2), in which case it is only considered as a user-defined conversion for direct-initialization (8.5) in certain contexts (13.3.1.4, 13.3.1.5, 13.3.1.6). [...]
Any comments on the above?
According to 8.5 and 13.3.1.3 the constructors of Y are considered and the best one is picked via overload resolution. In this case the relevant constructors are Y(int); and the copy and move constructors. In the process of overload resolution 13.3.2 Viable functions [over.match.viable] specifies this:
3 Second, for F to be a viable function, there shall exist for each argument an implicit conversion sequence (13.3.3.1) that converts that argument to the corresponding parameter of F. [...]
For all those constructors there is no such conversion from Z to either int or one of the flavours of Y. To convince ourselves, let's investigate what the Standard says about implicit conversion sequences in 13.3.3.1 Implicit conversion sequences [over.best.ics]:
1 An implicit conversion sequence is a sequence of conversions used to convert an argument in a function call to the type of the corresponding parameter of the function being called. The sequence of conversions is an implicit conversion as defined in Clause 4, which means it is governed by the rules for initialization of an object or reference by a single expression (8.5, 8.5.3).
If we cross-reference Clause 4, then we learn that an implicit conversion is defined in terms of copy-initialization (i.e. T t=e;, where T is int and e is z):
(§4.3) An expression e can be implicitly converted to a type T if and only if the declaration T t=e; is well-formed, for some invented temporary variable t (8.5). [...]
So I take 12.3.2:2 not to apply for this initialization, which happens in the larger context of a direct initialization. Doing otherwise would contradict with this latest paragraph.
As noted in Luc Danton's answer, implicit conversion is defined in terms of copy initialization. Then, if we look at 13.3.1.4:1[Copy-initialization of class by user-defined conversion]:
When the type of the initializer expression is a class type “cv S”,
the non-explicit conversion functions of S and its base classes are
considered. When initializing a temporary to be bound to the first
parameter of a constructor that takes a reference to possibly
cv-qualified T as its first argument, called with a single argument in
the context of direct-initialization, explicit conversion functions
are also considered. Those that are not hidden within S and yield a
type whose cv-unqualified version is the same type as T or is a
derived class thereof are candidate functions. Conversion functions
that return “reference to X” return lvalues or xvalues, depending on
the type of reference, of type X and are therefore considered to yield
X for this process of selecting candidate functions.
If I understand this correctly, the first one works because the conversion function yields a Y and is therefore a candidate function as noted by the second emphasized part in the quote, however, in your second case, the set of candidate functions is empty because there is no conversion function to Y and no non-explicit conversion functions as noted by the first emphasized part.
Concerning the third case:
After finding defect report 1087, it seems clear that the intention was to allow, copy, move and template constructors when direct-initializing an object of cv2 T as you mention. If you read the first passage of 13.3.1.4, it says Assuming that “cv1 T” is
the type of the object being initialized, with T a class type, so I think that implies of an object of type "cv2 T" that you mention. However, (after reading it over), it seems that the change due to the defect report has caused the wording to become vague and not cover the third case you propose.
I am no language lawyer, however the wording of the standard implies to me that marking a conversion operator as explicit requires that you explicity specify the conversion type (i.e. int) as part of the initialisation of object y1. With the code Y y1(z), it would appear that you're relying on an implicit conversion, since the type you specify for variable y1 is Y.
Therefore, I would expect correct usage of the explicit conversion operator in this situation to be:
Y y1( int(z) );
Or, since you are effectively specifying a cast, preferably
Y y1( static_cast<int> (z) );