为什么缩小不影响重载决议?

Why doesn't narrowing affect overload resolution?

考虑以下几点:

struct A {
    A(float ) { }
    A(int ) { }
};

int main() {
    A{1.1}; // error: ambiguous
}

编译失败并出现关于 A::A 的不明确重载的错误。两位候选人都被认为是可行的,因为 requirement is simply:

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.

虽然存在从 doubleint 的隐式转换序列,但 A(int ) 重载 实际上 不可行(在规范的,非 C++ 标准意义)——这将涉及缩小转换,因此格式不正确。

为什么在确定可行候选者的过程中不考虑缩小转换?尽管只有一个候选人可行,是否还有其他情况下过载被认为是模棱两可的?

问题在于可以不基于类型来检测缩小转换。

在 C++ 中编译时生成值的方法非常复杂。

阻止缩小转换是一件好事。让 C++ 的重载决议比现在更复杂是一件坏事。

在确定重载解析时忽略缩小转换规则(这使得重载解析纯粹是关于类型的),然后在选定的重载导致缩小转换时出错,防止重载解析变得更加复杂,并添加一个检测和防止缩小转化率的方法。

只有一个候选者可行的两个示例是在实例化期间失败 "late" 的模板函数和复制列表初始化(其中考虑了 explicit 构造函数,但如果选择它们,你得到一个错误)。同样,具有这种影响的重载解析会使重载解析比现在更加复杂。

现在,有人可能会问,为什么不将缩小转换完全纳入类型系统?

使缩小转换完全基于类型是不可行的。此类更改可能会破坏编译器可以 证明 有效的大量 "legacy" 代码。当大多数错误都是实际错误,而不是新的编译器版本是个混蛋时,扫描代码库所需的努力就更值得了。

unsigned char buff[]={0xff, 0x00, 0x1f};

这在基于类型的缩小转换下会失败,因为 0xffint 类型,这样的代码很常见。

如果这样的代码需要将 int 字面量毫无意义地修改为 unsigned char 字面量,很可能扫描会以我们设置一个标志告诉编译器闭嘴这个愚蠢的错误而结束.

  • 缩小是编译器只知道内置类型的东西。用户定义的隐式转换不能标记为缩小或不缩小。

  • 首先不应允许缩小转换是隐式的。 (不幸的是,它是 C 兼容性所必需的。这已通过 {} 禁止缩小内置类型的初始化得到了一些纠正。)

鉴于这些,重载规则不必费心提及这种特殊情况是有道理的。这可能是偶尔的方便,但并不是那么有价值。 IMO 一般来说,在重载决策中涉及的因素越少越好,并且拒绝更多的不明确的事情,迫使程序员明确地解决这些事情。


此外,当双精度不是常量表达式或双精度太大时,双精度到浮点数是一种缩小转换。

#include <iostream>
#include <iomanip>

int main() {
    double d{1.1};
    float f{d};
    std::cout << std::setprecision(100) << d << " " << f << '\n';
}

这通常会产生错误:

main.cpp:7:13: error: non-constant-expression cannot be narrowed from type 'double' to 'float' in initializer list [-Wc++11-narrowing]
    float f{d};
            ^