转换运算符:gcc 与 clang

Hen*_*enk 8 c++ language-lawyer implicit-conversion

考虑以下代码(https://godbolt.org/z/s17aoczj6):

template<class T>
class Wrapper {
    public:
    explicit Wrapper(T t): _value(t) {}

    template<class S = T>
    operator T() { return _value; }
    private:
    T _value;
};

auto main() -> int
{
    auto i = int{0};
    auto x = Wrapper<int>(i);
    return x + i;
}
Run Code Online (Sandbox Code Playgroud)

它使用 clang 进行编译,但不能使用 gcc(所有版本)进行编译。当删除template<class S = T>. 这段代码是否格式错误或者某个编译器错误?

gcc 中的错误是error: no match for 'operator+' (operand types are 'Wrapper<int>' and 'int') return x + i;.

我想要转换为T. 在此示例中,模板不是必需的,但在非最小示例中,我想使用 SFINAE,因此这里需要一个模板。

Art*_*yer 7

当你有x + i, 因为x是类类型时,重载解析开始:

标准中的具体细节([over.match.oper]p2)

如果任一操作数的类型为类或枚举,则可能会声明实现此运算符的用户定义的运算符函数,或者可能需要用户定义的转换才能将操作数转换为适合内置类型的类型。在运算符中。在这种情况下,重载决策用于确定要调用哪个运算符函数或内置运算符来实现该运算符。

内置候选者在第 3.3 段中定义:

对于operator ,、一元operator &operator ->,内置候选集为空。对于所有其他运算符,内置候选运算符包括 [over.built] 中定义的所有候选运算符函数,与给定运算符相比,

  • 具有相同的操作员名称,并且
  • 接受相同数量的操作数,并且
  • 接受给定操作数可以根据 [over.best.ics] 转换为的操作数类型,以及
  • 不具有与非函数模板特化的任何非成员候选者相同的参数类型列表。

根据 [over.built]p13,内置候选函数可能包括:

对于每对类型 L 和 R,其中 L 和 R 都是浮点型或提升整型,存在以下形式的候选运算符函数

LR      operator*(L, R);
...
LR      operator+(L, R);
...
bool    operator>=(L, R);
Run Code Online (Sandbox Code Playgroud)

其中 LR 是类型 L 和 R 之间常见算术转换 ([expr.arith.conv]) 的结果。

所以有一个内置函数int operator+(int, int)

至于可能的隐式转换序列有哪些:

[over.best.ics]p3:

格式良好的隐式转换序列是以下形式之一:

  • 标准转换序列,
  • 用户定义的转换序列,或
  • 省略号转换序列。

这里使用了用户定义的转换序列,由 [over.ics.user] 定义:

用户定义的转换序列由初始标准转换序列、用户定义的转换 ([class.conv]) 和第二个标准转换序列组成。

int(这里,两个标准转换序列都是空的,并且可以使用您的用户定义的转换)

因此,在检查是否int operator+(int, int)是内置候选者时,这是因为您的类类型和int.


至于实际的重载解析,来自[over.match.oper]:

  1. 某些运算符 @ 的重载决策的候选函数集是该运算符 @ 的成员候选函数、非成员候选函数、内置候选函数和重写候选函数的并集。
  2. 参数列表包含运算符的所有操作数。根据[over.match.viable]和[over.match.best]从候选函数集中选择最佳函数。

显然int operator+(int, int)是最好的匹配,因为它不需要第二个参数的转换,只需要用户定义的第一个参数的转换,所以它击败了其他候选者,long operator+(long, int)long operator+(int, long)


您可以看到内置候选集为空的问题,因为 GCC 错误报告没有可行的候选集。如果你有:

auto add(int a, int b) -> int
{
    return a + b;
}

auto main() -> int
{
    auto i = int{0};
    auto x = Wrapper<int>(i);
    return add(x, i);
}
Run Code Online (Sandbox Code Playgroud)

它现在可以与 GCC 很好地编译,因为它::add(int, int)被认为是候选者,尽管它应该与内置运算符没有什么不同int operator+(int, int)

如果你有:

    template<class S = T>
    operator S() { return _value; }  // Can convert to any type
Run Code Online (Sandbox Code Playgroud)

Clang 现在出现错误:

<source>:16:14: error: use of overloaded operator '+' is ambiguous (with operand types 'Wrapper<int>' and 'int')
    return x + i;
           ~ ^ ~
<source>:16:14: note: built-in candidate operator+(float, int)
<source>:16:14: note: built-in candidate operator+(double, int)
<source>:16:14: note: built-in candidate operator+(long double, int)
<source>:16:14: note: built-in candidate operator+(__float128, int)
<source>:16:14: note: built-in candidate operator+(int, int)
<source>:16:14: note: built-in candidate operator+(long, int)
<source>:16:14: note: built-in candidate operator+(long long, int)
<source>:16:14: note: built-in candidate operator+(__int128, int)
<source>:16:14: note: built-in candidate operator+(unsigned int, int)
<source>:16:14: note: built-in candidate operator+(unsigned long, int)
<source>:16:14: note: built-in candidate operator+(unsigned long long, int)
<source>:16:14: note: built-in candidate operator+(unsigned __int128, int)
Run Code Online (Sandbox Code Playgroud)

(请注意,此错误消息不包括第二个参数的转换,但由于永远不会选择这些转换,因此它们可能不被视为优化)

GCC 仍然表示根本没有候选者,尽管所有这些内置候选者都存在。

  • @亨克是的。我还发现这里发生完全相同的问题:/sf/ask/3475713391/ 和类似的问题:/sf/ask/1757059881/ (2认同)