【问题标题】:Distinguishing between user-defined conversion sequences by the initial standard conversion sequence通过初始标准转换序列区分用户定义的转换序列
【发布时间】:2012-07-18 08:22:33
【问题描述】:

该标准似乎提供了两条规则来区分涉及用户定义的转换运算符的隐式转换序列:

13.3.3 最佳可行函数[over.match.best]

[...] 一个可行函数 F1 被定义为比另一个可行函数更好的函数 F2 如果 [...]

  • 上下文是由用户定义的转换初始化(见 8.5、13.3.1.5 和 13.3.1.6)和 从 F1 的返回类型到目标类型的标准转换序列(即, 实体被初始化)是比标准转换序列更好的转换序列 F2 的返回类型到目标类型。

13.3.3.2 对隐式转换序列进行排名[over.ics.rank]

3 - 两个相同形式的隐式转换序列是无法区分的转换序列,除非其中之一 以下规则适用:[...]

  • 如果用户定义的转换序列 U1 包含相同的用户定义的转换函数或构造函数或聚合,则它们是比另一个用户定义的转换序列 U2 更好的转换序列 U1的初始化和第二个标准转换顺序优于第二个标准 U2的转换序列。

据我了解,13.3.3允许编译器区分不同的用户定义的转换运算符,而13.3.3.2允许编译器区分不同的函数 (某些函数 f 的重载),每个函数都需要在其参数中进行用户定义的转换(请参阅我对 Given the following code (in GCC 4.3) , why is the conversion to reference called twice? 的侧边栏)。

还有其他规则可以区分用户定义的转换序列吗? https://stackoverflow.com/a/1384044/567292 的答案表明 13.3.3.2:3 可以根据隐式对象参数(转换运算符)或构造函数的单个非默认参数的 cv 限定来区分用户定义的转换序列或聚合初始化,但我不明白这有什么关系,因为这需要在相应用户定义的转换序列的第一个标准转换序列之间进行比较,标准似乎没有提及。

假设 S1 优于 S2,其中 S1 是 U1 的第一个标准转换序列,S2 是 U2 的第一个标准转换序列,是不是 U1 优于 U2?换句话说,这段代码格式正确吗?

struct A {
    operator int();
    operator char() const;
} a;
void foo(double);
int main() {
    foo(a);
}

g++ (4.5.1)、Clang (3.0) 和 Comeau (4.3.10.1) 接受它,更喜欢非 const 限定的 A::operator int(),但我希望它会被拒绝模棱两可,因此格式不正确。这是标准的缺陷还是我对它的理解?

【问题讨论】:

  • 嗯,char -> doubleint -> double 都是可行的,排名相同的默认转化。但是A -> intconst A -> char 更受欢迎,因为它不需要额外的转换A -> const A
  • @KerrekSB 我接受 S1 优于 S2,其中 S1 是 U1 的第一个标准转换序列,S2 是 U2 的第一个标准转换序列。但是为什么会说 U1 比 U2 好呢?

标签: c++11 c++ constructor implicit-conversion language-lawyer conversion-operator


【解决方案1】:

这里的诀窍是,从类类型转换为非类类型实际上并没有将任何用户定义的转换列为隐式转换序列。

struct A {
    operator int();
    operator char() const;
} a;
void foo(double);
int main() {
    foo(a);
}

在表达式foo(a) 中,foo 显然命名了一个非重载的非成员函数。该调用需要使用单个表达式 adouble 类型的函数参数进行复制初始化 (8.5p14),该表达式是类类型 A 的左值。

由于目标类型 double 不是 cv 限定的类类型,但源类型 A 是,候选函数由第 13.3.1.5 节定义,S=AT=double。只考虑类AA 的任何基类中的转换函数。如果满足以下条件,则转换函数在候选集中:

  • 它没有隐藏在A类中,并且
  • 不是explicit(因为上下文是复制初始化),并且
  • 标准转换序列可以将函数的返回类型(不包括任何引用限定符)转换为目标类型double

好的,两个转换函数都符合条件,所以候选函数是

A::operator int();        // F1
A::operator char() const; // F2

使用 13.3.1p4 中的规则,每个函数都将隐式对象参数作为其参数列表中的唯一内容。 F1 的参数列表是“(对A 的左值引用)”,F2 的参数列表是“(对const A 的左值引用)”。

接下来我们检查功能是否可行(13.3.2)。每个函数的参数列表中都有一种类型,并且只有一个参数。每个参数/参数对是否有隐式转换序列?当然:

  • ICS1(F1):将隐式对象参数(类型为A 的左值引用)绑定到表达式aA 类型的左值)。
  • ICS1(F2):将隐式对象参数(对const A 的类型左值引用)绑定到表达式aA 类型的左值)。

由于没有进行派生到基础的转换,因此这些引用绑定被视为身份转换 (13.3.3.1.4p1) 的特殊情况。是的,这两个功能都是可行的。

现在我们必须确定一个隐式转换序列是否比另一个更好。这属于 13.3.3.2p3 大列表中的第五个子项:除了顶级 cv-qualifiers 之外,两者都是对同一类型的引用绑定。由于ICS1(F2) 的引用类型比ICS1(F1) 的引用类型更具有cv 限定,因此ICS1(F1) 优于ICS1(F2)

因此F1A::operator int() 是最可行的函数。甚至没有用户定义的转换(严格定义由SCS+(转换构造函数或转换函数)+SCS组成的ICS类型)进行比较。

现在,如果 foo 被重载,则需要比较参数 a 上的用户定义转换。因此,用户定义的转换(身份 + A::operator int() + intdouble)将与其他隐式转换序列进行比较。

【讨论】:

  • 很好的解释,谢谢。我将阅读用户定义的转换序列实际应用的地方。
  • 你的 ICS 是指隐式转换序列?
  • @AngelusMortis 是,ICS=隐式转换序列,SCS=标准转换序列。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多