15

Both clang and gcc accept the following code and choose A::operator B*.

struct B
{
};

struct A : B
{
    operator A*();
    operator B*();
};

A a;
void* x = a;

My reading of the standard - specifically sentences highlighted below in bold - suggests that this conversion should be ambiguous.

Both A::operator A* and A::operator B* are candidates for overload resolution because A* and B* are both convertible to void* via a standard conversion. Because the implied object parameter A& is the only argument, only the conversion sequence that converts from the implied object argument to the implied object parameter is considered - the type yielded by the conversion function is ignored. In both cases, the implied object argument is the initializer expression's type A, and the implied object parameter is A&. If both conversion sequences are identical, there is no way to distinguish between the two candidates.

8.5 Initializers [dcl.init]

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 destination type is a [reference/array/class...] [deleted details not applicable to this scenario]

— Otherwise, if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated (13.3.1.5), and the best one is chosen through overload resolution (13.3). The user-defined conversion so selected is called to convert the initializer expression into the object being initialized. If the conversion cannot be done or is ambiguous, the initialization is ill-formed.

13.3.1.5 Initialization by conversion function [over.match.conv]

Under the conditions specified in 8.5, as part of an initialization of an object of nonclass 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:

— 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 (13.3.3.1.1) are candidate functions. For direct-initialization, those explicit conversion functions that are not hidden within S and yield type T or a type that can be converted to type T with a qualification conversion (4.4) are also candidate functions. Conversion functions that return a cv-qualified type are considered to yield the cv-unqualified version of that type for this process of selecting 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.

The argument list has one argument, which is the initializer expression. [ Note: This argument will be compared against the implicit object parameter of the conversion functions. —end note ]

Is this ambiguous according to the standard?

EDIT: note that this is a similar question, but not the same as Distinguishing between user-defined conversion sequences by the initial standard conversion sequence

The difference being that in my example both conversion functions have the same qualification.

Community
  • 1
  • 1
willj
  • 2,991
  • 12
  • 24
  • Both conversion functions return a pointer to the same base address of an instance which a conversion to `void*` cause. Try multiple inheritance and an additional conversion operator and the conversion should be ambiguous. – Captain Obvlious Aug 06 '13 at 20:51
  • @CaptainObvlious How do you know what they return? [They do not even need to be implemented to test this question](http://coliru.stacked-crooked.com/view?id=ad1ba686b7a271079f6ca6c5e91f5c02-e1204655eaff68246b392dc70c5a32c9). – Casey Aug 06 '13 at 20:57
  • @Casey Because the conversion operators have a return type which specifies what they return. – Captain Obvlious Aug 06 '13 at 21:10
  • 1
    @CaptainObvlious Knowing the return types doesn't allow one to deduce that they "return a pointer to the same base address of an instance". – Casey Aug 06 '13 at 21:21

1 Answers1

9

TLDR: When everything else is equal, overload resolution breaks the tie by which conversion function has the best conversion from its return value to the target type.


All references are to ISO/IEC 14882:2011 (C++11). The behavior of the initialization:

void* x = a;

is defined as follows. First, this is an initialization as described in 8.5 Initializers [dcl.init] and conforms to the grammar described in p1. Since the destination type void* is a non-class type, and the source type A is a class type, this specific initializer is of the form described in 8.5 p16, bullet 7:

Otherwise, if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated (13.3.1.5), and the best one is chosen through overload resolution (13.3). The user-defined conversion so selected is called to convert the initializer expression into the object being initialized. If the conversion cannot be done or is ambiguous, the initialization is ill-formed.

The "enumeration of applicable conversion functions" is detailed in 13.3.1.5 p1:

Under the conditions specified in 8.5, as part of an initialization of an object of nonclass 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:

  • 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 (13.3.3.1.1) are candidate functions. For direct-initialization, those explicit conversion functions that are not hidden within S and yield type T or a type that can be converted to type T with a qualification conversion (4.4) are also candidate functions. Conversion functions that return a cv-qualified type are considered to yield the cv-unqualified version of that type for this process of selecting 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.

Note that both A::operator A*() and A::operator B*() are candidate functions, since both A* and B* are convertible to void* per 4.10p2: "A prvalue of type “pointer to cv T,” where T is an object type, can be converted to a prvalue of type “pointer to cv void”." Given that both functions are candidates, overload resolution must choose between them.

Overload resolution is decribed in 13.3 [over.match]. p2 states:

Overload resolution selects the function to call in seven distinct contexts within the language:

...

  • invocation of a conversion function for initialization of an object of a nonclass type from an expression of class type (13.3.1.5)

...

Each of these contexts defines the set of candidate functions and the list of arguments in its own unique way. But, once the candidate functions and argument lists have been identified, the selection of the best function is the same in all cases:

  • First, a subset of the candidate functions (those that have the proper number of arguments and meet certain other conditions) is selected to form a set of viable functions (13.3.2).

  • Then the best viable function is selected based on the implicit conversion sequences (13.3.3.1) needed to match each argument to the corresponding parameter of each viable function.

Which of our two functions are viable? 13.3.2 [over.match.viable] p1:

From the set of candidate functions constructed for a given context (13.3.1), a set of viable functions is chosen, from which the best function will be selected by comparing argument conversion sequences for the best fit (13.3.3).

The requirements are presented in p2:

First, to be a viable function, a candidate function shall have enough parameters to agree in number with the arguments in the list.

and p3:

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.

Both requirements are trivially met by our conversion functions: they have a single (implicit) argument of the same type as the initializer expression a.

Determination of the best of the viable functions is described in 13.3.3 [over.match.best]. It defines some formalisms for describing conversion sequences, sequences of operations necessary to convert from the types of the actual function arguments to the types of the formal function parameters. In the case of our conversion functions, they both have exactly one parameter whose type is exactly that of the actual argument, so the "conversion sequence" corresponding to each overload is the identity sequence. They are discriminated by the language in 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

  • for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,

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

What about that final bullet? Does one of our overloads have a better standard conversion sequence from its return type to void*?

13.3.3.2 Ranking Implicit Conversion Sequences [over.ics.rank] p4 states in the second bullet point:

If class B is derived directly or indirectly from class A, conversion of B* to A* is better than conversion of B* to void*, and conversion of A* to void* is better than conversion of B* to void*.

This is exactly the case of the OP, except with the names A and B reversed. Overload resolution on the two conversion operators of the OP is resolved in favor of A::operator B*() since the cited rule makes the conversion sequence B*void* better than A*void*

Casey
  • 41,449
  • 7
  • 95
  • 125
  • This refers to ranking of the conversion sequences from the argument type(s) to the parameter type(s) of the candidate functions in overload resolution. In this scenario, neither `A*` or `B*` are argument types and `void*` is not the parameter type. Therefore there is no reason to compare these conversion sequences, according to my reading of the standard. – willj Aug 06 '13 at 22:14
  • @willj That's what I get for not showing my work. There's the entire argument for you. TLDR: When everything else is equal, overload resolution breaks the tie by which conversion function has the *best* conversion from its return value to the target type. – Casey Aug 07 '13 at 01:19
  • I am a bit confused about the C++ standard here. If B is derived from A, then I would consider the conversion of B* to void* to be better than the conversion of A* to void*. After all, B is the more specific type than A. As is, the standard basically "downgrades" the object in question as much as possible, before going into the void. – Kai Petzke Aug 11 '13 at 17:07
  • 1
    When you cast any pointer to `void*` you are inherently downgrading it no matter which pointer you originally had since `void*` has no knowledge of the type to which it points to. IMO the reasoning is the following: `void*` can point to anything, `A*` can point to objects of both A and B type, and `B*` can point only to objects of type B. – Diggy Aug 12 '13 at 21:32