C++20で比較演算子のオーバーロード解決のルールが大幅に変更になりました。
その影響で、boost::operators::equality_comparable2が特定のケースでコンパイルエラーが生じる結果となることが報告されています
*1。
問題のコードは大体こんな感じ。
#include <boost/operators.hpp>
struct my_type : boost::equality_comparable2<my_type, double> {
explicit my_type(double mem) : mem_{mem}{}
double mem_{0};
operator double() {
return mem_;
}
bool operator==(my_type l) {
return l.mem_ == mem_;
}
};
int main() {
my_type x{0};
return x == double{0};
}
(下記issueから引用)
どういうことかというと、C++20では、「x==y」という式に対して、「y==x」と式を書き換えた場合を加えてオーバーロード解決をしようとします。 すると、x==double{0}; という式に対して、double{0}==xという式に書き換えた場合の等価演算子も候補に入るわけですね。
ところで、boost::equality_comparable2<X, Y>は、 bool operator==(const Y& y, const X& x){ return x==y;} という形で、operator==(X, Y)のみ用意しておけば、自動的にoperator==(Y, X)も実装されるということです。
じゃあ、上のケースで、x==double{0}という式はどうなるか。C++17までであれば、mytypeからdoubleへの暗黙変換が可能なので、double同士の組み込みの比較演算子のみがマッチするため、それが選ばれるわけです。
ところが、C++20だと、x==double{0};が、double{0}==x;と書き換えられた式についてもオーバーロード解決にに加わるため、equality_comparable2内で定義されるfriend関数である、operator(const double&, const mytype&);がマッチしてしまうんですよね。しかも、double同士の組み込みの演算子と違い、変換なしでぴったり一致してしまうので、こちらが選ばれてしまうわけです。
すると、operator(const double&, const mytype&)内で、再びmytype == doubleの比較をしてしまうため、無限再帰をしてしまうわけです。
*1 https://github.com/boostorg/utility/issues/65