使用 `&&` 和 `const` 限定符重载 operator== 会导致 C++20 中的歧义
Overloading operator== with `&&` and `const` qualifier cause ambiguity in C++20
考虑具有相同 &&
限定符和不同 const
限定符的两个 operator==
重载的 struct
S
:
struct S {
bool operator==(const S&) && {
return true;
}
bool operator==(const S&) const && {
return true;
}
};
如果我将两个 S
与 operator==
进行比较:
S{} == S{};
gcc 和 msvc 接受此代码,clang rejects 它与:
<source>:14:7: error: use of overloaded operator '==' is ambiguous (with operand types 'S' and 'S')
S{} == S{};
~~~ ^ ~~~
为什么 clang 认为这里有一个不明确的重载决议?在这种情况下,非 const 不应该是最佳人选吗?
同样,如果我将两个 S
与合成的 operator!=
进行比较:
S{} != S{};
gcc 仍然接受此代码,但 msvc 和 clang doesn't:
<source>:14:7: error: use of overloaded operator '!=' is ambiguous (with operand types 'S' and 'S')
S{} != S{};
~~~ ^ ~~~
合成的operator!=
突然导致msvc的歧义似乎很奇怪。哪个编译器是正确的?
这个例子在 C++17 中是明确的。 C++20 带来的变化:
[over.match.oper]
For a unary operator @ with an operand of type cv1 T1, and for a binary operator @ with a left operand of type cv1 T1 and a right operand of type cv2 T2, four sets of candidate functions, designated member candidates, non-member candidates, built-in candidates, and rewritten candidates, are constructed as follows:
- ...
- 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.
The rewritten candidate set is determined as follows:
- ...
- For the equality operators, the rewritten candidates also include a synthesized candidate, with the order of the two parameters reversed, for each non-rewritten candidate for the expression y == x.
因此,改写后的候选集包括这些:
implicit object parameter
|||
(S&&, const S&); // 1
(const S&&, const S&); // 2
// candidates that match with reversed arguments
(const S&, S&&); // 1 reversed
(const S&, const S&&); // 2 reversed
重载 1 比 2 更匹配,但 1 的合成反向重载与原始非反向重载不明确,因为两者都具有到一个参数的 const 转换。请注意,即使重载 2 不存在,这实际上也是模棱两可的。
因此,Clang 是正确的。
这也包含在信息兼容性附件中:
Affected subclause: [over.match.oper] Change: Equality and inequality expressions can now find reversed and rewritten candidates.
Rationale: Improve consistency of equality with three-way comparison and make it easier to write the full complement of equality
operations.
Effect on original feature: Equality and inequality expressions between two objects of different types, where one is convertible to
the other, could invoke a different operator. Equality and inequality
expressions between two objects of the same type could become
ambiguous.
struct A {
operator int() const;
};
bool operator==(A, int); // #1
// #2 is built-in candidate: bool operator==(int, int);
// #3 is built-in candidate: bool operator!=(int, int);
int check(A x, A y) {
return (x == y) + // ill-formed; previously well-formed
(10 == x) + // calls #1, previously selected #2
(10 != x); // calls #1, previously selected #3
}
考虑具有相同 &&
限定符和不同 const
限定符的两个 operator==
重载的 struct
S
:
struct S {
bool operator==(const S&) && {
return true;
}
bool operator==(const S&) const && {
return true;
}
};
如果我将两个 S
与 operator==
进行比较:
S{} == S{};
gcc 和 msvc 接受此代码,clang rejects 它与:
<source>:14:7: error: use of overloaded operator '==' is ambiguous (with operand types 'S' and 'S')
S{} == S{};
~~~ ^ ~~~
为什么 clang 认为这里有一个不明确的重载决议?在这种情况下,非 const 不应该是最佳人选吗?
同样,如果我将两个 S
与合成的 operator!=
进行比较:
S{} != S{};
gcc 仍然接受此代码,但 msvc 和 clang doesn't:
<source>:14:7: error: use of overloaded operator '!=' is ambiguous (with operand types 'S' and 'S')
S{} != S{};
~~~ ^ ~~~
合成的operator!=
突然导致msvc的歧义似乎很奇怪。哪个编译器是正确的?
这个例子在 C++17 中是明确的。 C++20 带来的变化:
[over.match.oper]
For a unary operator @ with an operand of type cv1 T1, and for a binary operator @ with a left operand of type cv1 T1 and a right operand of type cv2 T2, four sets of candidate functions, designated member candidates, non-member candidates, built-in candidates, and rewritten candidates, are constructed as follows:
- ...
- 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.
The rewritten candidate set is determined as follows:
- ...
- For the equality operators, the rewritten candidates also include a synthesized candidate, with the order of the two parameters reversed, for each non-rewritten candidate for the expression y == x.
因此,改写后的候选集包括这些:
implicit object parameter
|||
(S&&, const S&); // 1
(const S&&, const S&); // 2
// candidates that match with reversed arguments
(const S&, S&&); // 1 reversed
(const S&, const S&&); // 2 reversed
重载 1 比 2 更匹配,但 1 的合成反向重载与原始非反向重载不明确,因为两者都具有到一个参数的 const 转换。请注意,即使重载 2 不存在,这实际上也是模棱两可的。
因此,Clang 是正确的。
这也包含在信息兼容性附件中:
Affected subclause: [over.match.oper] Change: Equality and inequality expressions can now find reversed and rewritten candidates.
Rationale: Improve consistency of equality with three-way comparison and make it easier to write the full complement of equality operations.
Effect on original feature: Equality and inequality expressions between two objects of different types, where one is convertible to the other, could invoke a different operator. Equality and inequality expressions between two objects of the same type could become ambiguous.
struct A { operator int() const; }; bool operator==(A, int); // #1 // #2 is built-in candidate: bool operator==(int, int); // #3 is built-in candidate: bool operator!=(int, int); int check(A x, A y) { return (x == y) + // ill-formed; previously well-formed (10 == x) + // calls #1, previously selected #2 (10 != x); // calls #1, previously selected #3 }