The following code compiles fine with clang-trunk in c++17 mode but breaks in c++2a (upcoming c++20) mode:
// Meta struct describing the result of a comparison
struct Meta {};
struct Foo {
Meta operator==(const Foo&) { return Meta{}; }
Meta operator!=(const Foo&) { return Meta{}; }
};
int main()
{
Meta res = (Foo{} != Foo{});
}
It also compiles fine with gcc-trunk or clang-9.0.0: https://godbolt.org/z/8GGT78
The error with clang-trunk and -std=c++2a
:
<source>:12:19: error: use of overloaded operator '!=' is ambiguous (with operand types 'Foo' and 'Foo')
Meta res = (f != g);
~ ^ ~
<source>:6:10: note: candidate function
Meta operator!=(const Foo&) {return Meta{};}
^
<source>:5:10: note: candidate function
Meta operator==(const Foo&) {return Meta{};}
^
<source>:5:10: note: candidate function (with reversed parameter order)
I understand that C++20 will make it possible to only overload operator==
and the compiler will automatically generate operator!=
by negating the result of operator==
. As far as I understand, this only works as long as the return type is bool
.
The source of the problem is that in Eigen we declare a set of operators ==
, !=
, <
, ... between Array
objects or Array
and Scalars, which return (an expression of) an Array of bool
(which can then be accessed element-wise, or used otherwise). E.g.,
#include <Eigen/Core>
int main()
{
Eigen::ArrayXd a(10);
a.setRandom();
return (a != 0.0).any();
}
In contrast to my example above this even fails with gcc-trunk: https://godbolt.org/z/RWktKs. I have not managed yet to reduce this to a non-Eigen example, which fails in both clang-trunk and gcc-trunk (the example at the top is quite simplified).
Related issue report: https://gitlab.com/libeigen/eigen/issues/1833
My actual question: Is this actually a breaking change in C++20 (and is there a possibility to overload the comparison operators to return Meta-objects), or is it more likely a regression in clang/gcc?
The Eigen issue appears to reduce to the following:
using Scalar = double;
template<class Derived>
struct Base {
friend inline int operator==(const Scalar&, const Derived&) { return 1; }
int operator!=(const Scalar&) const;
};
struct X : Base<X> {};
int main() {
X{} != 0.0;
}
The two candidates for the expression are
operator==(const Scalar&, const Derived&)
Base<X>::operator!=(const Scalar&) const
Per [over.match.funcs]/4, as operator!=
was not imported into the scope of X
by a using-declaration, the type of the implicit object parameter for #2 is const Base<X>&
. As a result, #1 has a better implicit conversion sequence for that argument (exact match, rather than derived-to-base conversion). Selecting #1 then renders the program ill-formed.
Possible fixes:
using Base::operator!=;
to Derived
, oroperator==
to take a const Base&
instead of a const Derived&
.