在运算符表达式上下文

时间:2017-08-09 23:55:23

标签: 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;
}

标准说明

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

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节进行解释。

C ++ 14的变化由CWG 1687引入。

我天真的解释

我最初认为顶级代码在C ++ 14中应该是错误的。根据标准,我对顶级代码重载解析过程的天真理解是这样的(部分编号来自N4659):

首先生成候选函数集。它包含用户定义的运算符#116.3.1.2/(3.2))和内置运算符#216.3.1.2/(3.3)16.6/14)。接下来,为了确定可行功能集,通过为每个参数/参数对构造隐式转换序列(ICS)来测试两个运算符的可行性。所有ICS都成功构建为ICS1(#1) = int* → X16.3.3.1.2,用户定义的转换序列),ICS2(#2) = Y → double → int(用户定义的转换序列),ICS1(#2) = int* → int*({{3} },身份转换,标准转换序列之一)和ICS2(#2) = X → double → std::ptrdiff_t(用户定义的转换序列),因此两个运算符都是可行的。然后,通过比较ICS选择最佳可行功能;由于ICS1(#2)优于ICS1(#1)16.3.3.1/6)且ICS2(#2)不比ICS2(#1)16.3.3.2/(2.1))差,#2是比#116.3.3.2/3)更好的功能。最后,通过重载决策(16.3.3/1)选择内置运算符#2

当选择内置运算符时,上面引用的规则(16.3.3/2)适用:将ICS应用于参数后,对运算符表达式的处理将转移到第8章[expr]。这里ICS的应用在C ++ 11和C ++ 14中有所不同。在C ++ 11中,ICS完全应用,因此考虑(int*) y + (std::ptrdiff_t) (double) n,并且它很好。而在C ++ 14中,未应用用户定义的转换序列中的第二个标准转换序列,因此考虑(int*) y + (double) n。这会导致语义规则违规(16.3.1.2/7),即表达式格式错误,需要实施才能发出诊断消息。

Clang的解释

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

海湾合作委员会的解释

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

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

问题

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

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

脚注

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

更新

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

1 个答案:

答案 0 :(得分:4)

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

operator+(X, int);                // #1
operator+(int*, std::ptrdiff_t ); // #2

#1需要两个用户定义的转换序列,#2需要标准转换序列(完全匹配,但并不重要)和用户定义的转换序列。对于第一个参数,标准转换序列is better than是用户定义的转换序列,而对于第二个参数,两个序列是无法区分的(没有these conditions适用)。由于#2中的第一个隐式转换序列优于#1中的第一个隐式转换序列,而第二个转换序列是等效的,#2 wins

然后在CWG 1687之后,我们don't performdoubleptrdiff_t的最后一次转换,因此结果应该是格式错误的。

回答这个问题:

  

是CWG 1687的行为吗?

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

int *b = Y() + X();             // error: pointer arithmetic requires integral operand

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