因为有两种方法在C ++中定义转换,当同一转换有两种可能性时,它们如何交互? [英] Since there are two ways to define a conversion in C++ how do they interact when there are two possibilities for the same conversion?

查看:127
本文介绍了因为有两种方法在C ++中定义转换,当同一转换有两种可能性时,它们如何交互?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



在C ++中,你可以说类型A应该以两种不同的方式隐式转换为类型B.



如果你是A的作者,你可以这样添加到A:

 运算子B(){
// code
}

如果您是B的作者,您可以在B中添加如下内容:

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

如果我理解正确,那么将允许A隐式转换为B.因此,如果两者都定义了哪一个被使用?这是允许的吗?



注意:我知道你可能永远不会在这样做的情况下。你可以使构造函数显式,或者更有可能只有两个中的一个。我只是想知道C ++规范是什么,我不知道如何查找。

解决方案

这个问题可能比你想要的更复杂。的确,编译器会拒绝不明确的转换,因为Lightness Races in Orbit指出,但转换是否含糊不清?让我们来看几个例子。所有引用均为C ++ 11标准。



显式转换



直接因为你问隐式转换,但由于Lightness Races in Orbit给出了一个显式转换的例子,我将继续。



显式转换是从<当时:




    li>您使用语法(B)a ,其中 a 类型 / code>,在这种情况下将等价于 static_cast (a)(C ++ 11标准,§5.4/ 4) li>
  • 您使用静态转型,在这种情况下将创建一个临时,它以与声明 B t(a); initializes t ; (§5.2.9/ 4)

  • 您使用语法 B(a),等效于(B)a ,因此也与声明中的初始化相同的事情 B t(a); (§5.2.3/ 1)



因此,在每种情况下,直接初始化类型 B 使用类型 A 的值作为参数。 §8.5/ 16指定仅考虑构造函数,因此将调用 B :: B(const A&)。 (有关详情,请参阅我的回答: http://stackoverflow.com/a/22444974/481267



复制初始化



复制初始化

  B b = a; 

a $ c>使用用户定义的转换序列首先将转换为类型 B 的临时,这是一个隐式转换序列。然后这个临时用于直接初始化 b



因为这是类类型的复制初始化不同类类型的对象转换构造函数 B :: B(const A&)和转换函数 A :: operator B()是转换的候选者(§13.3.1.4)。后者被称为是因为它赢得了重载分辨率 。注意,如果 B :: B 有参数 A& ,而不是 const A& code>,重载将是不明确的,程序不会编译。有关标准的详细信息和参考,请参阅此答案: https://stackoverflow.com/a/1384044/481267



复制列表初始化



复制列表初始化

  B b = {a}; 

只考虑 B .4 / 3),而不是 A 的转换函数,因此 B :: B(const A&)



函数参数的隐式转换



如果我们有

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

那么编译器必须选择最佳的隐式转换序列来转换 / code>键入 B ,以将其传递给 f 。为此,考虑用户定义的转换序列,其包括标准转换,随后是用户定义的转换,接着是另一个标准转换(§13.3.3.1.2/ 1)。用户定义的转换可以通过转换构造函数 B :: B(const A&)或通过转换函数 A :: operator B ()



这里是棘手的地方。在标准中有一些令人困惑的措辞:


由于隐式转换序列是初始化,初始化的特殊规则
用户定义的转换适用于为用户定义的转换
序列(见13.3.3和13.3.3.1)选择最佳的用户定义转换。


(§13.3.3.1.2/ 2)



为了简短起见,这意味着用户定义的转换从 A B 的用户定义转换序列本身受到重载解析; A :: operator B()胜过 B :: B(const A&)如果我们有 B :: B(A&)而不是 B,则会导致资格限制(如在复制初始化情况下) :B(const A&)。请注意,这不会导致重载解析的无限递归,因为不允许用户定义的转换将参数转换为用户定义转换的参数类型。



返回语句



  B foo $ b return A(); 
}

表达式 A()隐式转换为类型 B (§6.6.3/ 2),因此相同的规则适用于函数参数的隐式转换; A ::运算符B()将被调用,如果我们有 B :: B(A&)。但是,如果它是

  return {A()}; 

那么这将是一个复制列表初始化(§6.6.3/ 2)。 B :: B(const A&)将被调用。



处理异常时不尝试用户定义的转换; catch(B) block不会处理 throw A();


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.

解决方案

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: http://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 ++中定义转换,当同一转换有两种可能性时,它们如何交互?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆