【问题标题】:Correct behavior of built-in operator candidates of overload resolution in the operator expression context运算符表达式上下文中重载决策的内置运算符候选者的正确行为
【发布时间】:2017-08-09 23:55:22
【问题描述】:

目前我正在尝试理解 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 中的顶级代码应该是格式错误的。按照标准,我对top code重载解析过程的幼稚理解是这样的(节号来自N4659):

首先生成候选函数集。它包含用户定义的运算符#1 (16.3.1.2/(3.2)) 和一个内置的运算符#2 (16.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*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

选择内置操作员时,上面引用的规则(@ 987654333)适用:在将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的解释

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

我的猜测是 GCC 认为 #2 不可行,然后只有可行的功能是 #1。但是我找不到像内置运算符这样的规则,当它在用户定义的转换序列中没有第二个标准转换序列的情况下变得不正确时,它是不可行的。事实上,当 CWG 1687 引入短语“除了用户定义的转换序列的第二个标准转换序列”时,似乎对生存能力的定义没有其他修改。 p>

问题

问题一:按照现行标准,哪个是正确的解释?

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


脚注

  • [1]:不要默默地破坏用 C++03 编写的现有代码,这种行为是不可取的。这可能是 CWG 1687 决定仅禁用第二个标准转换序列而保留生存能力定义的原因。请参阅下面的 cmets。

更新

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

【问题讨论】:

  • 我想说 gcc 在这里是错误的。如果将其发布到版本中,它将破坏许多现有代码。该标准禁止为指针类型重载算术运算符。
  • @Michael Roy > 它会破坏很多现有代码 哦,我明白了。这就是 CWG 1687 没有修改生存能力定义的原因。谢谢!

标签: c++ operator-overloading c++14 c++17


【解决方案1】:

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

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

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

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


回答这个问题:

行为是否符合 CWG 1687 的预期?

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

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

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

【讨论】:

  • 非常感谢您的解释和错误报告!您是否有计划为 Visual Studio 2017 制作与当前 GCC 行为相同的错误报告?
  • @akinomyoga 从来没有使用过 Windows,所以甚至不知道在哪里提交这样的错误报告。
  • 谢谢。那我稍后再做报告。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-16
  • 1970-01-01
  • 2020-01-03
  • 2017-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多