在重载解析中,选择使用不明确转换序列的函数是否必然会导致调用格式不正确
In overload resolution, does selection of a function that uses the ambiguous conversion sequence necessarily result in the call being ill-formed?
是在我研究这个SO问题的答案时出现的。请考虑以下代码:
struct A{
operator char() const{ return 'a'; }
operator int() const{ return 10; }
};
struct B {
void operator<< (int) { }
};
int main()
{
A a;
B b;
b << a;
}
a
到int
的转换可以通过a.operator char()
后进行整体晋升,也可以a.operator int()
然后进行身份转换(即根本没有转换(。该标准说(§13.3.3.1 [over.best.ics]/p10,脚注省略,粗体;所有引号均来自N3936(:
如果存在多个不同的转换序列,则每个转换序列都转换 参数类型的参数,隐式转换序列 与参数相关联的参数被定义为唯一转换 序列指定不明确的转换序列。对于 对隐式转换序列进行排名的目的,如中所述 13.3.3.2,不明确的转换序列被视为用户定义的序列,与任何其他序列都无法区分 用户定义的转换序列。如果函数使用 选择模糊转换序列作为最佳可行函数, 调用将格式不正确,因为转换了其中一个 调用中的参数不明确。
在这里,B::operator<<(int)
是唯一可行的候选对象 - 因此是最佳可行的候选对象,即使参数的转换序列是不明确的转换序列。根据粗体句子,调用应该是格式不正确的,因为"调用中其中一个参数的转换是模棱两可的"。
然而,我测试过的编译器(g++、clang 和 MSVC(实际上都没有报告错误,这是有道理的,因为在通过重载解析选择要调用的函数后,函数的"参数 (8.3.5( 应初始化 (8.5, 12.8, 12.1( 与其对应的参数"(§5.2.2 [expr.call]/p4(。此初始化是复制初始化 (§8.5 [dcl.init]/p15(,根据 §8.5 [dcl.init]/p17,导致新一轮重载解析,以确定要使用的转换函数:
初始值设定项的语义如下所示。目标类型 是正在初始化的对象或引用的类型,并且 源类型是初始值设定项表达式的类型。如果初始值设定项不是单个(可能是括号(表达式,则 未定义源类型。
- [...]
- 如果目标类型是(可能符合 cv 条件的(类类型:[...]
- 否则,如果源类型是(可能符合 cv 条件的(类类型,则考虑转换函数。适用的转换 枚举函数(13.3.1.5(,并选择最佳函数 通过过载解析 (13.3(。用户定义的转换因此 调用 Selected 以将初始值设定项表达式转换为 正在初始化的对象。如果转换无法完成或 模棱两可,初始化格式不正确。
- [...]
在这一轮重载解决中,§13.3.3 [over.match.best]/p1 中有一个决胜局:
一个可行的函数
F1
被定义为比另一个更好的函数 可行函数 F2 如果对于所有参数i
,ICSi(F1)
不是更糟 转换顺序比ICSi(F2)
,然后
- 对于某些参数
j
,ICSj(F1)
是比ICSj(F2)
更好的转换序列,或者,如果不是这样,- 上下文是通过用户定义的转换(请参阅 8.5、13.3.1.5 和 13.3.1.6(和从返回类型
F1
到目标类型的标准转换序列(即 正在初始化的实体(是比 从返回类型F2
到 目标类型。(示例和列表的其余部分省略(
由于从 int
到 int
(完全匹配排名(的标准转换序列优于从 char
到 int
(升级排名(的标准转换序列,因此第一个优于第二个,并且应该没有歧义 - operator int()
定义的转换将用于初始化,然后与 §13.3.3.1 [over.best.ics]/p10 中的句子相矛盾,该句子表示函数调用由于歧义而格式不正确。
上面的分析有什么问题,还是那句话是标准中的错误?
在为用户定义的转换序列确定最佳用户定义转换时,我们有一个重载候选集。
§13.3.3/p1 说:
定义 ICSi(F( 如下:
[...]
让 ICSi(F( 表示将列表中的第 i 个参数转换为 可行函数 F 的第 i 个参数的类型。 13.3.3.1 定义隐式转换序列和 13.3.3.2 定义一个隐式转换序列是更好的转换序列的含义 或比另一个更差的转换顺序。
给定这些定义,可行函数 F1 被定义为比另一个可行函数更好的函数 F2 如果对于所有参数 i,ICSi(F1( 不是比 ICSi(F2( 更差的转换序列,然后
— [...]
— 上下文是通过用户定义的转换进行初始化(请参阅 8.5、13.3.1.5 和 13.3.1.6(和 从返回类型 F1 到目标类型的标准转换序列(即 正在初始化的实体(是比标准转换序列更好的转换序列 将 F2 的类型返回到目标类型。
这适用于以下情况
§13.3.3.1.2/p2
2 第二个标准转换序列将用户自定义转换的结果转换为目标 序列的类型。由于隐式转换序列是初始化,因此 通过用户定义的转换进行初始化 在为用户定义的转换选择最佳用户定义转换时应用 转换顺序(见13.3.3和13.3.3.1(。
因此,涉及operator int
的转换序列被选为最佳匹配。
最后,我将 §13.3.3.1/p10 改写为
如果存在多个不同的转换序列,每个转换序列都将参数转换为参数类型,并且无法确定最佳候选项, 与参数关联的隐式转换序列定义为唯一转换序列 指定不明确的转换序列。为了对隐式转换序列进行排名 如 13.3.3.2 中所述,不明确的转换序列被视为用户定义的序列,即 与任何其他用户定义的转换序列134无法区分。如果使用不明确的函数 选择转换序列作为最佳可行函数,调用将格式不正确,因为转换 调用中的一个参数是模棱两可的。
- 在C++STL中是否有Polyval(Matlab函数)等价物?
- 是否有C++编译器选项允许激进地删除所有函数调用,并将参数传递给具有空体的函数
- 是否有类似std::lower_bound的函数,而不需要排序/分区输入
- 函数作为模板参数,是否对返回类型强制约束
- visual是否可以在c++中创建一个接收无限数量相同类型(或至少相当数量)参数的函数
- 编译器如何在使用SFINAE的函数和标准函数之间确定两者是否可行
- 函数是否可以访问传递给main()的参数
- 是否可以将llvm::FunctionType转换为C/C++原始函数指针
- 在这种情况下,java对象是否可以调用本机函数
- 检查函数返回类型是否与STL容器类型值相同
- 根据某个函数是否存在启用模板
- 在C++中,使用带有 std::optional 参数的函数<T>来表示可选参数是否有意义?
- 无论如何,我可以确定构造函数是否存在吗?
- 是否可以将函数导入命名空间,但不能导出它?
- 返回指向对象的指针的函数调用是否为 prvalue?
- 是否可以依赖函数范围的静态变量来执行程序关闭期间调用的方法?
- 重载运算符的范围是什么?它是否会影响作为类成员的集合的插入函数?
- 是否有任何建议来统一函数类型限定符并简化可恶的函数类型?
- 在函数范围内在堆栈上分配的数组在离开函数时是否总是被释放?
- 表达式 SFINAE:如何根据类型是否包含具有一个或多个参数的函数来选择模板版本