转换运算符:gcc vs clang

Conversion operator: gcc vs clang

考虑以下代码 (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;
}

它用 clang 编译,但不能用 gcc(所有版本)编译。 它在删除 template<class S = T> 时在 gcc 中工作。 此代码格式错误还是某个编译器错误?

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

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

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

具体细节来自标准 ([over.match.oper]p2)

If either operand has a type that is a class or an enumeration, a user-defined operator function might be declared that implements this operator or a user-defined conversion can be necessary to convert the operand to a type that is appropriate for a built-in operator. In this case, overload resolution is used to determine which operator function or built-in operator is to be invoked to implement the operator.

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

For the operator ,, the unary operator &, or the operator ->, the built-in candidates set is empty. For all other operators, the built-in candidates include all of the candidate operator functions defined in [over.built] that, compared to the given operator,

  • have the same operator name, and
  • accept the same number of operands, and
  • accept operand types to which the given operand or operands can be converted according to [over.best.ics], and
  • do not have the same parameter-type-list as any non-member candidate that is not a function template specialization.

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

For every pair of types L and R, where each of L and R is a floating-point or promoted integral type, there exist candidate operator functions of the form

LR      operator*(L, R);
...
LR      operator+(L, R);
...
bool    operator>=(L, R);

where LR is the result of the usual arithmetic conversions ([expr.arith.conv]) between types L and R.

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

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

[over.best.ics]p3:

A well-formed implicit conversion sequence is one of the following forms:

  • a standard conversion sequence,
  • a user-defined conversion sequence, or
  • an ellipsis conversion sequence.

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

A user-defined conversion sequence consists of an initial standard conversion sequence followed by a user-defined conversion ([class.conv]) followed by a second standard conversion sequence.

(这里,两个标准转换序列都是空的,可以使用你自定义的转换为int

因此,当检查 int operator+(int, int) 是否为内置候选时,是因为在您的 class 类型和 int 之间存在转换。


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

  1. The set of candidate functions for overload resolution for some operator @ is the union of the member candidates, the non-member candidates, the built-in candidates, and the rewritten candidates for that operator @.
  2. The argument list contains all of the operands of the operator. The best function from the set of candidate functions is selected according to [over.match.viable] and [over.match.best].

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


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

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);
}

它现在可以很好地与 GCC 编译,因为 ::add(int, int) 被认为是一个候选者,即使它应该与内置运算符 int operator+(int, int).

没有什么不同

如果你有:

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

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)

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

而且 GCC 仍然说根本没有候选者,即使所有这些内置候选者都存在。