使用C++20在gcc和MSVC/clang之间重载模板相等比较运算符的不同结果

zsbz8rwp  于 2022-11-24  发布在  其他
关注(0)|答案(1)|浏览(196)

考虑以下等式运算符的实现,使用C++20编译(在godbolt上运行):

#include <optional>

template <class T>
struct MyOptional{
    bool has_value() const { return false;}
    T const & operator*() const { return t; }
    T t{};
};

template <class T>
bool operator==(MyOptional<T> const & lhs, std::nullopt_t)
{
    return !lhs.has_value();
}

template <class U, class T>
bool operator==(U const & lhs, MyOptional<T> const & rhs)
{
    // gcc error: no match for 'operator==' (operand types are 'const std::nullopt_t' and 'const int')
    return rhs.has_value() ? lhs == *rhs : false;
}

int main(){
    MyOptional<int> o1;
    bool compiles = o1 == std::nullopt;
    bool doesNotCompile = std::nullopt == o1; // gcc fails
}

clang 15和MSVC 19.33编译此代码时都没有错误,但gcc 12.2给出了

<source>: In instantiation of 'bool operator==(const U&, const MyOptional<T>&) [with U = std::nullopt_t; T = int]':
<source>:26:43:   required from here
<source>:20:34: error: no match for 'operator==' (operand types are 'const std::nullopt_t' and 'const int')
   20 |     return rhs.has_value() ? lhs == *rhs : false;
      |                              ~~~~^~~~~~~
<source>:11:6: note: candidate: 'template<class T> bool operator==(const MyOptional<T>&, std::nullopt_t)' (reversed)
   11 | bool operator==(MyOptional<T> const & lhs, std::nullopt_t)
      |      ^~~~~~~~
<source>:11:6: note:   template argument deduction/substitution failed:
<source>:20:34: note:   mismatched types 'const MyOptional<T>' and 'const int'
   20 |     return rhs.has_value() ? lhs == *rhs : false;
      |                              ~~~~^~~~~~~
<source>:17:6: note: candidate: 'template<class U, class T> bool operator==(const U&, const MyOptional<T>&)' (reversed)
   17 | bool operator==(U const & lhs, MyOptional<T> const & rhs)
      |      ^~~~~~~~
<source>:17:6: note:   template argument deduction/substitution failed:
<source>:20:34: note:   'const std::nullopt_t' is not derived from 'const MyOptional<T>'
   20 |     return rhs.has_value() ? lhs == *rhs : false;
      |

gcc显然认为第二个重载与std::nullopt == o1更匹配,并试图调用它。
对我来说,并不清楚哪个编译器是正确的。C++20引入了在比较等式时参数的自动反转。据我所知,当编译器看到std::nullopt == o1时,它也会考虑o1 == std::nullopt。然而,当考虑参数的反转顺序时,我找不到任何语句。
1.编译器是否应该首先搜索std::nullopt == o1的有效调用,如果找到了,就直接使用它,而不搜索o1 == std::nullopt?在这种情况下,我想gcc是正确的?
1.或者,编译器总是考虑std::nullopt == o1反向参数o1 == std::nullopt,并从可行函数集中选择“最佳”匹配(如果原始参数和反向参数匹配不同的重载,则无论“最佳”在这里是什么意思)?我猜,在这种情况下MSVC和clang是正确的?
所以,问题是:哪种编译器是正确的,在匹配过程中什么时候考虑参数的逆序?

vkc1a9a2

vkc1a9a21#

重写的候选项与未重写的候选项同时被考虑。如果两个候选项都不符合较高优先级的规则,则在重载解决规则中只有一个延迟平局决胜规则。(有关完整的决策链,请参见[over.match.best.general]/2。)
如果至少有一个参数到参数的转换序列被认为比另一个候选序列更好,并且没有一个被认为更差,则认为一个候选序列比另一个候选序列更好。在您的情况下,所有转换序列都是同样好的。
因此需要考虑平局决胜规则。在考虑候选是否是重写的平局决胜规则之前的下一个相关平局决胜规则是形成候选的模板的偏序(由于两个候选都来自模板,因此在此应用)。
模板的偏序考虑一个模板是否比另一个模板更专业化,这基本上是询问一个模板接受的参数列表集是否是另一个模板接受的参数列表集的严格子集(实际规则相当复杂)。
因此,这里的问题是,究竟应该比较哪些模板?如果我们只是比较您编写的两个模板,那么没有一个模板更专业。但如果我们考虑到一个候选模板是根据模板重写的,那么这个测试可能也应该考虑到模板的参数是相反的?在这种情况下,第一个模板会更专业。因为它只接受一种类型作为它的第一个参数,而另一个接受任何类型,所以应该选择第一个模板。
如果我们不考虑这种反转,那么我们将陷入平局决胜局,考虑重写,并且因为第一个模板必须被重写以成为运算符的候选,所以将选择第二个模板。
Clang和MSVC遵循部分顺序的反转,而GCC不遵循。
这是CWG 2445,它被解析为声明应该执行此反转,以便Clang和MSVC是正确的。GCC似乎还没有实现缺陷报告。

相关问题