Since there are two ways to define a conversion in C++ how do they interact when there are two possibilities for the same conversion?

StackOverflow https://stackoverflow.com/questions/22444415

Question

I am just looking for clarification on how C++ works, this isn't really about solving a particular problem in my code.

In C++ you can say that type A should implicitly convert to type B in two different ways.

If you are the author of A you can add something like this to A:

operator B() {
   // code
}

If you are the author of B you can add something like this to B:

B(const A &a) {
    // code
}

Either one of these, if I understand correctly, will allow A to implicitly convert to B. So if both are defined which one is used? Is that even allowed?

NOTE: I understand that you should probably never be in a situation where you do this. You would either make the constructor explicit or even more likely only have one of the two. I am just wondering what the C++ specification says and I don't know how to look that up.

Was it helpful?

Solution

Unfortunately, the answer to this question is probably more complex than what you were looking for. It is true that the compiler will reject ambiguous conversions as Lightness Races in Orbit points out, but are the conversions ambiguous? Let's examine a few cases. All references are to the C++11 standard.

Explicit conversion

This doesn't address your question directly because you asked about implicit conversion, but since Lightness Races in Orbit gave an example of explicit conversion, I'll go over it anyway.

Explicit conversion is performed from A to B when:

  • you use the syntax (B)a, where a is of type A, which in this case will be equivalent to static_cast<B>(a) (C++11 standard, §5.4/4).
  • you use a static cast, which in this case will create a temporary which is initialized in the same way that the declaration B t(a); initializes t; (§5.2.9/4)
  • you use the syntax B(a), which is equivalent to (B)a and hence also does the same thing as the initialization in the declaration B t(a); (§5.2.3/1)

In each case, therefore, direct initialization is performed of a prvalue of type B using a value of type A as the argument. §8.5/16 specifies that only constructors are considered, so B::B(const A&) will be called. (For slightly more detail, see my answer here: https://stackoverflow.com/a/22444974/481267)

Copy-initialization

In the copy-initialization

B b = a;

the value a of type A is first converted to a temporary of type B using a user-defined conversion sequence, which is an implicit conversion sequence. Then this temporary is used to direct-initialize b.

Because this is copy-initialization of a class type by an object of a different class type, both the converting constructor B::B(const A&) and the conversion function A::operator B() are candidates for the conversion (§13.3.1.4). The latter is called because it wins overload resolution. Note that if B::B had argument A& rather than const A&, the overload would be ambiguous and the program wouldn't compile. For details and references to the Standard see this answer: https://stackoverflow.com/a/1384044/481267

Copy-list-initialization

The copy-list-initialization

B b = {a};

only considers constructors of B (§8.5.4/3), and not conversion functions of A, so B::B(const A&) will be called, just like in explicit conversion.

Implicit conversion of function arguments

If we have

void f(B b);
A a;
f(a);

then the compiler has to select the best implicit conversion sequence to convert a to type B in order to pass it to f. For this purpose, user-defined conversion sequences are considered which consist of a standard conversion followed by a user-defined conversion followed by another standard conversion (§13.3.3.1.2/1). A user-defined conversion can occur through either the converting constructor B::B(const A&) or through the conversion function A::operator B().

Here's where it gets tricky. There is some confusing wording in the standard:

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 13.3.3 and 13.3.3.1).

(§13.3.3.1.2/2)

To make a long story short, this means that the user-defined conversion in the user-defined conversion sequence from A to B is itself subject to overload resolution; A::operator B() wins over B::B(const A&) because the former has less cv-qualification (as in the copy-initialization case) and ambiguity would result if we had had B::B(A&) rather than B::B(const A&). Note that this cannot result in the infinite recursion of overload resolution, since user-defined conversions are not allowed for converting the argument to the parameter type of the user-defined conversion.

Return statement

In

B foo() {
    return A();
}

the expression A() is implicitly converted to type B (§6.6.3/2) so the same rules apply as in implicit conversion of function arguments; A::operator B() will be called, and the overload would be ambiguous if we had B::B(A&). However, if it were instead

return {A()};

then this would be a copy-list-initialization instead (§6.6.3/2 again); B::B(const A&) will be called.

Note: user-defined conversions are not tried when handling exceptions; a catch(B) block won't handle a throw A();.

OTHER TIPS

[C++11: 12.3/2]: User-defined conversions are applied only where they are unambiguous. [..]

12.3 goes on to list the two kinds you identified.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top