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

℡╲_俬逩灬. 提交于 2019-12-04 04:31:02
Brian

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

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!