在运算符表达式上下文中纠正重载决策的内置运算符候选者的行为

aki*_*oga 8 c++ operator-overloading c++14 c++17

目前我正在尝试理解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;
}
Run Code Online (Sandbox Code Playgroud)

标准说明

以下是标准版本中相应段落的引用:

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
Run Code Online (Sandbox Code Playgroud)

- 结束例子]

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)更好.最后,#2通过重载分辨率(16.3.3/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的解释

Clang选择#2并调用它,没有任何关于8.7/1违规的诊断消息.我的猜测是Clang在将调用转移到内置规则(8.7/1)之前完全将ICS应用于参数,这是一个错误.

海湾合作委员会的解释

GCC选择#1没有诊断.Visual Studio 2017中的Microsoft C/C++编译器似乎表现相同.此外,这种行为对我来说似乎是合理的(编辑:见[1]).

我的猜测是GCC认为#2不可行,然后只有可行的功能#1.但我找不到任何规则,如果内置运算符在用户定义的转换序列中没有第二个标准转换序列而变得格式不正确时就不可行.实际上,当CWG 1687引入短语" 除了用户定义的转换序列的第二标准转换序列 " 之外,似乎在可行性的定义中没有其他修改.

问题1:根据现行标准,哪个是正确的解释?

问题2:如果我的天真解释是正确的,那么CWG 1687的行为是否正确?


脚注

  • [1]:不要无声地破坏用C++ 03编写的现有代码,这种行为是不可取的.这可能就是为什么CWG 1687决定只禁用第二个标准转换序列而不是原样定义生存能力的原因.见下面的评论.

更新

在此问题之后,针对以下编译器报告了此问题:

Bar*_*rry 4

我同意你的解释。我们有类型的参数int*,并且Y有两个候选人:

operator+(X, int);                // #1
operator+(int*, std::ptrdiff_t ); // #2
Run Code Online (Sandbox Code Playgroud)

#1需要两个用户定义的转换序列,#2需要一个标准转换序列(精确匹配,尽管并不重要)和一个用户定义的转换序列。对于第一个参数,标准转换序列比用户定义的转换序列更好,而对于第二个参数,两个序列无法区分(这些条件都不适用)。由于 中 的第一个隐式转换序列#2优于 中的第一个隐式转换序列#1,并且第二个转换序列等效,#2 因此获胜

然后在 CWG 1687 之后,我们不执行double从到 的最后一次转换ptrdiff_t,因此结果应该是格式错误的。


回答这个问题:

这是 CWG 1687 预期的行为吗?

我怀疑肯定是这样,因为这个例子是:

int *b = Y() + X();             // error: pointer arithmetic requires integral operand
Run Code Online (Sandbox Code Playgroud)

这与您的示例非常相似 - 唯一的区别是Y()可以转换为int*直接being int*。我继续提交了gcc 81789llvm 34138。请注意,clang 根本不实现 CWG 1687、该问题和标准编译中的示例。