Explicit conversion functions, direct-initialization, and converting constructors - c++

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

Related

Implicit conversion with multiple intermediate conversion steps [duplicate]

class C {
public:
C() { }
};
class B {
public:
B(C c) { }
B() { }
};
class A {
public:
A(bool b) { }
A(B b) { }
};
int main() {
A a1 = true; // bool -> A is allowed
A a2 = B(); // B -> A is allowed
A a3 = 7; // int -> bool -> A is allowed
A a4 = C(); // C -> B -> A isn't allowed
}
Why I can use two-step implicit conversion with bool but can't use it with C?
What is the general rule describing multistep implicit conversion?
There is no multi-step user-defined implicit conversion.
int -> bool -> A
is allowed because the int->bool conversion isn't user-defined.
12.3 Conversions [class.conv]
1 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 4), for
initialization (8.5), and for explicit type conversions (5.4, 5.2.9).
2 User-defined conversions are applied only where they are unambiguous
(10.2, 12.3.2). Conversions obey the access control rules (clause 11).
Access control is applied after ambiguity resolution (3.4).
3 [ Note:
See 13.3 for a discussion of the use of conversions in function calls
as well as examples below. —end note ]
4 At most one user-defined
conversion (constructor or conversion function) is implicitly applied
to a single value.
Since this construction is perfectly legal
A a4((C()));
problem is, that you use copy initization. Really, your example is equal to
A a4((A(C()));
8.5/16
The semantics of initializers are as follows. The destination type is the type of the object or reference being
initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly
parenthesized) expression, the source type is not defined.
If the destination type is a (possibly cv-qualified) class type:
— 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).
13.3.1.4/1
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.
13.3.3.1/4
However, when considering the argument of a constructor or user-defined conversion function that is a
candidate by 13.3.1.3 when invoked for the copying/moving of the temporary in the second step of a class
copy-initialization, by 13.3.1.7 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, or by 13.3.1.4, 13.3.1.5, or 13.3.1.6 in all cases, only
standard conversion sequences and ellipsis conversion sequences are considered.
Your user-defined conversion (C -> B) is not considered in this case.

Why does user-defined conversion applied during the initialization?

The following code works fine:
#include <iostream>
struct B
{
operator int()
{
return int();
}
};
struct A
{
A(int, int){ std::cout << "A(int, int)" << std::endl; }
};
A a({B(), B()});
int main()
{
}
and produces output:
A(int, int)
DEMO
But I can't get why? What the Standard says is:
However, when considering the argument of a constructor or
user-defined conversion function that is a candidate by 13.3.1.3 when
invoked for the copying/moving of the temporary in the second step of
a class copy-initialization, by 13.3.1.7 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 [...] only standard conversion sequences and ellipsis conversion sequences are considered
So in our case we considered the argument of the constructor (it was {B(), B()}). More precisely, we passed initializer-list as a single argument (the second case in the rule I cited). Now, we need to convert the first element of the initializer-list (temporary of type B) to int and the only way to do that is to apply user-defined convertion (B::operator int()). But, as said at the end of rule that I cited only standard conversion sequences and ellipsis conversion sequences was considered. Since, that code shouldn't work, it should throw the error like A(int, int) is not viable or sort of.
What's wrong. May be it's a bug?
The wording was defective and changed with C++14. Now [over.best.ics]/4 reads
However, if the target is
the first parameter of a constructor or
[…]
and the constructor or user-defined conversion function is a candidate
by
13.3.1.3, when the argument is the temporary in the second step of a class copy-initialization,
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, 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. [ Note: These
rules prevent more than one user-defined conversion from being applied
during overload resolution, thereby avoiding infinite recursion. —
end note ]
The conversion of B() to int is not covered by this - the bold phrase only appertains to the binding of a reference to a temporary during copy-initialization. However, Clang rejects this sample code according to the above:
class A;
struct B
{
operator A();
};
struct A
{
A(A const&){}
};
A a{B()};

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

Distinguishing between user-defined conversion sequences by the initial standard conversion sequence

The standard appears to provide two rules for distinguishing between implicit conversion sequences that involve user-defined conversion operators:
c++11
13.3.3 Best viable function [over.match.best]
[...] a viable function F1 is defined to be a better function than another viable function
F2 if [...]
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.
13.3.3.2 Ranking implicit conversion sequences [over.ics.rank]
3 - Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of
the following rules applies: [...]
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 or aggregate
initialization and the second standard conversion sequence of U1 is better than the second standard
conversion sequence of U2.
As I understand it, 13.3.3 allows the compiler to distinguish between different user-defined conversion operators, while 13.3.3.2 allows the compiler to distinguish between different functions (overloads of some function f) that each require a user-defined conversion in their arguments (see my sidebar to Given the following code (in GCC 4.3) , why is the conversion to reference called twice?).
Are there any other rules that can distinguish between user-defined conversion sequences? The answer at https://stackoverflow.com/a/1384044/567292 indicates that 13.3.3.2:3 can distinguish between user-defined conversion sequences based on the cv-qualification of the implicit object parameter (to a conversion operator) or of the single non-default parameter to a constructor or aggregate initialisation, but I don't see how that can be relevant given that that would require comparison between the first standard conversion sequences of the respective user-defined conversion sequences, which the standard doesn't appear to mention.
Supposing that S1 is better than S2, where S1 is the first standard conversion sequence of U1 and S2 is the first standard conversion sequence of U2, does it follow that U1 is better than U2? In other words, is this code well-formed?
struct A {
operator int();
operator char() const;
} a;
void foo(double);
int main() {
foo(a);
}
g++ (4.5.1), Clang (3.0) and Comeau (4.3.10.1) accept it, preferring the non-const-qualified A::operator int(), but I'd expect it to be rejected as ambiguous and thus ill-formed. Is this a deficiency in the standard or in my understanding of it?
The trick here is that converting from a class type to a non-class type doesn't actually rank any user-defined conversions as implicit conversion sequences.
struct A {
operator int();
operator char() const;
} a;
void foo(double);
int main() {
foo(a);
}
In the expression foo(a), foo obviously names a non-overloaded non-member function. The call requires copy-initializing (8.5p14) the function parameter, of type double, using the single expression a, which is an lvalue of class type A.
Since the destination type double is not a cv-qualified class type but the source type A is, the candidate functions are defined by section 13.3.1.5, with S=A and T=double. Only conversion functions in class A and any base classes of A are considered. A conversion function is in the set of candidates if:
It is not hidden in class A, and
It is not explicit (since the context is copy-initialization), and
A standard conversion sequence can convert the function's return type (not including any reference qualifiers) to the destination type double.
Okay, both conversion functions qualify, so the candidate functions are
A::operator int(); // F1
A::operator char() const; // F2
Using the rules from 13.3.1p4, each function has the implicit object parameter as the only thing in its parameter list. F1's parameter list is "(lvalue reference to A)" and F2's parameter list is "(lvalue reference to const A)".
Next we check that the functions are viable (13.3.2). Each function has one type in its parameter list, and there is one argument. Is there an implicit conversion sequence for each argument/parameter pair? Sure:
ICS1(F1): Bind the implicit object parameter (type lvalue reference to A) to expression a (lvalue of type A).
ICS1(F2): Bind the implicit object parameter (type lvalue reference to const A) to expression a (lvalue of type A).
Since there's no derived-to-base conversion going on, these reference bindings are considered special cases of the identity conversion (13.3.3.1.4p1). Yup, both functions are viable.
Now we have to determine if one implicit conversion sequence is better than the other. This falls under the fifth sub-item in the big list in 13.3.3.2p3: both are reference bindings to the same type except for top-level cv-qualifiers. Since the referenced type for ICS1(F2) is more cv-qualified than the referenced type for ICS1(F1), ICS1(F1) is better than ICS1(F2).
Therefore F1, or A::operator int(), is the most viable function. And no user-defined conversions (with the strict definition of a type of ICS composed of SCS + (converting constructor or conversion function) + SCS) were even to be compared.
Now if foo were overloaded, user-defined conversions on the argument a would need to be compared. So then the user-defined conversion (identity + A::operator int() + int to double) would be compared to other implicit conversion sequences.

Ambiguous assignment operator

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 ]