运算符表达式上下文中重载解析的内置运算符候选项的正确行为
Correct behavior of built-in operator candidates of overload resolution in the operator expression context
目前我试图理解C++标准中的段落[over.match.oper]/7,但遇到了以下GCC和Clang产生不同结果的情况:
https://wandbox.org/permlink/WpoMviA4MHId7iD9
#include <iostream>
void print_type(int) { std::cout << "int" << std::endl; }
void print_type(int*) { std::cout << "int*" << std::endl; }
struct X { X(int*) {} };
struct Y { operator double() { return 0.0; } };
int operator+(X, int) { return 0; } // #1
// T* operator+(T*, std::ptrdiff_t); // #2: a built-in operator (N4659 16.6/14)
int main() {
int* p = 0;
Y y;
print_type(p + y); // This line produces different results for different compilers:
// - gcc HEAD 8.0.0 : always "int" (#1 is called)
// - clang HEAD 6.0.0 : always "int*" (#2 is called)
// - my understanding : "int*" until C++11, ill-formed since C++14
return 0;
}
标准中的描述
以下是标准版本中相应段落的引用:
C++1z (N4659) 16.3.1.2 [over.match.oper] 第 7 段(与C++14 (N4140) 13.3.1.2 [over.match.oper] 第 7
段基本相同):
如果通过重载解析选择内置候选项,则类类型的操作数将转换为所选操作函数的相应参数的类型,但未应用用户定义转换序列 (16.3.3.1.2) 的第二个标准转换序列。然后将运算符视为相应的内置运算符,并根据条款 8 进行解释。[示例:
struct X { operator double(); }; struct Y { operator int*(); }; int *a = Y() + 100.0; // error: pointer arithmetic requires integral operand int *b = Y() + X(); // error: pointer arithmetic requires integral operand
- 完结]
C++03 13.3.1.2 [over.match.oper] 第7 段(与 C++11(N3291) 13.3.1.2 [over.match.oper] 第 7
段基本相同):
如果通过重载解析选择内置候选项,则操作数将转换为所选操作函数的相应参数的类型。然后将运算符视为相应的内置运算符,并根据条款 5 进行解释。
CWG 1687引入了C++14的变化。
我的幼稚解释
我最初认为顶级代码在 C++14 中应该格式不正确。根据标准,我对顶级代码重载分辨率过程的天真理解是这样的(节号来自N4659):
首先生成候选函数集。它包含用户定义的运算符#1
(16.3.1.2/(3.2)) 和一个内置运算符#2
(16.3.1.2/(3.3), 16.6/14)。接下来,为了确定可行函数的集合,通过为每个参数/参数对构建隐式转换序列 (ICS) 来测试两个运算符的可行性;所有 ICS 都已成功构造为ICS1(#1) = int* → X
(16.3.3.1.2,用户定义的转换序列)、ICS2(#2) = Y → double → int
(用户定义的转换序列)、ICS1(#2) = int* → int*
(16.3.3.1/6,身份转换,标准转换序列之一)和ICS2(#2) = X → double → std::ptrdiff_t
(用户定义的转换序列),因此这两个运算符都是可行的。然后,通过比较ICS选择最佳可行函数;由于ICS1(#2)
比ICS1(#1)
(16.3.3.2/(2.1))好,ICS2(#2)
不比ICS2(#1)
差(16.3.3.2/3),因此#2
比#1
(16.3.3/1)更好的函数。最后,通过重载分辨率 (16.3.3/2) 选择内置运算符#2
。
选择内置运算符时,应用上面引用的规则 (16.3.1.2/7):将 ICS 应用于参数后,运算符表达式的处理将转移到第 8 条 [expr]。在这里,ICS的应用在C++11和C++14中有所不同。在 C++11 中,ICS 被完全应用,因此考虑(int*) y + (std::ptrdiff_t) (double) n
,这很好。而在 C++14 中,用户定义的转换序列中的第二个标准转换序列不应用,因此考虑(int*) y + (double) n
。这会导致语义规则冲突 (8.7/1),即表达式格式不正确,需要实现才能发出诊断消息。
叮的解释
Clang 选择#2
并调用它,而不对 8.7/1 违规进行任何诊断消息。我的猜测是 Clang 在将调用转移到内置规则 (8.7/1) 之前完全将 ICS 应用于参数,这是一个错误。
海湾合作委员会的解释
GCC 选择不带诊断#1
。Visual Studio 2017 中的Microsoft C/C++编译器似乎行为相同。
我的猜测是GCC认为#2
是不可行的,然后只有可行的功能是#1
的。但是我找不到任何规则,例如内置运算符在用户定义的转换序列中没有第二个标准转换序列的情况下变得格式不正确时是不可行的。事实上,当CWG 1687引入短语">除了用户定义的转换序列的第二个标准转换序列"时,似乎在可行性的定义中没有其他修改。
问题
问题1:根据现行标准,哪种解释是正确的?
问题 2:如果我的幼稚解释是正确的,那么 CWG 1687 的行为是否有意为之?
脚注
- [1]:为了不以静默方式破坏 C++03 中编写的现有代码,这种行为是不希望的。这可能就是CWG 1687决定禁用第二个标准转换序列而保留可行性定义的原因。请参阅下面的评论。
更新
在此问题之后,以下编译器报告了此问题:
- 海湾合作委员会 GCC 81789
- 叮当 34138
- MSC VisualStudio 92207
我同意你的解释。我们有int*
和Y
类型的参数,我们有两个候选者:
operator+(X, int); // #1
operator+(int*, std::ptrdiff_t ); // #2
#1
需要两个用户定义的转化序列,#2
需要一个标准转换序列(完全匹配,但无关紧要)和一个用户定义的转换序列。对于第一个参数,标准转换序列优于用户定义的转换序列,而对于第二个参数,两个序列是无法区分的(这些条件都不适用)。由于#2
中的第一个隐式转换序列优于#1
中的第一个隐式转换序列,并且第二个转换序列是等效的,因此#2
胜出。
然后在CWG 1687之后,我们不执行从double
到ptrdiff_t
的最后一次转换,因此结果应该是格式错误的。
要回答这个问题:
该行为是CWG 1687有意为之吗?
我怀疑肯定是这样,因为这个例子是:
int *b = Y() + X(); // error: pointer arithmetic requires integral operand
与您的示例非常相似 - 唯一的区别是Y()
可以转换为int*
而不是直接int*
。 我继续提交 gcc 81789 和 llvm 34138。请注意,clang 根本不实现 CWG 1687、该问题中的示例以及标准编译中的示例。
- 如何在选项卡视图Qt中设置一个新项目,并保存以前的项目
- 为什么比较运算符如此快速
- C++映射:具有自定义类的运算符[]不起作用(总是返回0)
- 使用C++中的模板和运算符重载执行矩阵运算
- 为什么这个运算符<重载函数对 STL 算法不可见?
- Win32编译器选项和内存分配
- 增量运算符与后缀混淆
- 一个关于在C++中重载布尔运算符的问题
- 运算符C++ "delete []"仅删除 2 个前值
- 模板类无法识别友元运算符
- 我可以使用条件运算符初始化C风格的字符串文字吗
- 关闭||运算符优化
- 通过继承类使用来自不同命名空间的运算符
- C++Cast运算符过载
- C/C++预处理器是否可以检测一些编译器选项
- C++, = 运算符不同的选项
- 提升程序选项:自定义验证器是否需要重载运算符>>?
- C++重载运算符两次,一个返回非常量引用,另一个返回常量引用,首选项是什么
- 复制构造函数和赋值运算符实现选项 -
- C++运算符重载选项