operator==() code compiles w/C++17 but not C++20 - c++

This code snippet
#include <stdlib.h>
struct Base { };
template<typename T>
inline bool operator==(const T&, const Base&)
{
return true;
}
template<typename T>
inline bool operator!=(const T& lhs, const Base& rhs)
{
return !(lhs == rhs);
}
struct A : public Base
{
bool operator==(const A&) const { return true; }
};
struct A_1 final : public A { };
int main()
{
const A a;
const A_1 a_1;
if (a_1 != a) {}
return EXIT_SUCCESS;
}
Compiles without errors in C++17 (Visual Studio 2022).
(See C++17 operator==() and operator!=() code fails with C++20 for a more detailed example; note in that case the code compiles.)
Trying to build the same code with C++20 generates three compiler errors:
error C2666: 'operator !=': 3 overloads have similar conversions
message : could be 'bool A::operator ==(const A &) const' [rewritten expression '!(x == y)']
message : or 'bool A::operator ==(const A &) const' [synthesized expression '!(y == x)']
message : or 'bool operator !=<A_1>(const T &,const Base &)'
Yes, I understand that C++20 will synthesize operator!= and the like ... but shouldn't existing C++17 code still compile with C++20? How can I fix things so that the same code compiles with both C++17 and C++20 and generates identical results?
Making changes to derived class could be difficult as that code could be elsewhere (this is actually library code); it would be much preferred to make changes to Base.

This is because all of the functions are now candidate, and before they weren't.
This means that suddently, the operator== in A is a candidate for a_1 != a. The compiler is now allowed to invert the arguments, change a == b to !(a != b) and vice versa, and even change the order to b == a.
The code result in an ambiguous call since bool operator==(const A&); in A supports a_1 != a, by inverting the operation to !(a_1 == a) and changing parameter order to finally have !(a == a_1) which is a candidate.
There are multiple solution to this.
One is to simply make one candidate better by inheriting the function:
struct A_1 final : public A { using A::operator==; };
The other would be to restraint the operator to work only with A:
struct A : public Base
{
friend bool operator==(std::same_as<A> auto const&, std::same_as<A> auto const&) { return true; }
};
Another solution not requirering changing A or A_1 would be to add an overload that is always an ideal candidate inside Base as a friend function.
The whole code becomes this in C++20:
struct Base {
friend bool operator==(std::derived_from<Base> auto const&, std::derived_from<Base> auto const&) { return true; }
};
struct A : Base {};
struct A_1 final : A {};
You can remove the template function in the global namespace, and you can also remove the function in the derived classes too.
You are not required to remove the function in A, but it won't be call and the resulting code will be quite surprising still.
However, note that your code was broken before.
This is the compiler output for code using the operator== in C++17:
int main()
{
const A a;
const A_1 a_1;
if (!(a_1 == a)) {}
return EXIT_SUCCESS;
}
Compiler output:
<source>: In function 'int main()':
<source>:26:15: error: ambiguous overload for 'operator==' (operand types are 'const A_1' and 'const A')
26 | if (!(a_1 == a)) {}
| ~~~ ^~ ~
| | |
| | const A
| const A_1
<source>:17:10: note: candidate: 'bool A::operator==(const A&) const'
17 | bool operator==(const A&) const { return true; }
| ^~~~~~~~
<source>:5:13: note: candidate: 'bool operator==(const T&, const Base&) [with T = A_1]'
5 | inline bool operator==(const T&, const Base&)
| ^~~~~~~~
ASM generation compiler returned: 1
C++20 will just accept less code that has a surprising behaviour.

It's fine if you remove A::operator==.
There's really no point to A::operator==, as the template is saying that everything is equal to a Base.
but shouldn't existing C++17 code still compile with C++20?
The committee works hard to minimise breaking changes, but they don't promise there will be none. In this case it was felt that having == be an equivalence relation was more important than maintaining existing behaviour. As the linked question notes, polymorphic equality testing is often a source of bugs.

C++ extends overload resolution for relational operator to include swapped arguments, which is in the diagnostic message:
GCC marks the candidate "(reversed)" and
Clang mentions (with reversed parameter order)
So you need fewer operators. In fact, you could consider overloading / defaulting the three-way comparison operator (<=>) instead.

Related

Inaccessible operator == breaks child class with spaceship operator

I bumped in GCC error, which can be demonstrated with the following simplified program. Base struct A has an operator == with some argument type other than A. Child struct B defines default spaceship operator <=>, which shall implicitly define operator == as well. But since no appropriate equality operator is found in the base class, B::operator == must be implicitly deleted. Instead, the resulting struct B appears broken in a way that no object of it can be created:
#include <compare>
struct A {
std::strong_ordering operator <=>(const A &) const = default;
bool operator==(int) const;
};
struct B : A {
virtual std::strong_ordering operator <=>(const B &) const noexcept = default;
};
B b; //GCC error here
Demo: https://gcc.godbolt.org/z/584nhfxxs
Error message:
error: use of deleted function 'virtual constexpr bool B::operator==(const B&) const'
9 | virtual std::strong_ordering operator <=>(const B &) const noexcept = default;
| ^~~~~~~~
<source>:9:34: note: 'virtual constexpr bool B::operator==(const B&) const' is implicitly deleted because the default definition would be ill-formed:
<source>:8:8: error: no match for 'operator==' (operand types are 'A' and 'A')
8 | struct B : A {
| ^
<source>:5:10: note: candidate: 'bool A::operator==(int) const' (reversed)
5 | bool operator==(int) const;
| ^~~~~~~~
<source>:5:21: note: no known conversion for argument 1 from 'A' to 'int'
5 | bool operator==(int) const;
Is it simply a bug in GCC?

Recursive spaceship operator

I have written this simple code but it does not compile because the comparison is implicit deleted.
struct Tree {
std::vector<Tree> child;
friend auto operator<=>(const Tree &a, const Tree &b) = default;
}
int main(){
Tree t;
std::cout<<(t<t)<<std::endl;
}
Can anybody explain to me how to fix the problem or at least why it does not work?
Edit: compiled with "g++ -std=gnu++2a main.cpp"
Edit2:
Here is the error part of the output (it is followed by many, many lines of candidates):
main.cpp: In function 'int main()':
main.cpp:31:25: error: use of deleted function 'constexpr auto operator<=>(const Tree&, const Tree&)'
31 | std::cout << (tmp < tmp) << std::endl;
| ^~~
main.cpp:12:17: note: 'constexpr auto operator<=>(const Tree&, const Tree&)' is implicitly deleted because the default definition would be ill-formed:
12 | friend auto operator<=>(const Tree &a, const Tree &b) = default;
| ^~~~~~~~
main.cpp:12:17: error: no match for 'operator<=>' (operand types are 'std::vector<Tree>' and 'std::vector<Tree>')
You cannot usually define a recursive function with a deduced return type. In order to deduce it, you need to already know it, so this is a no go.
Replacing auto with an explicit return type std::weak_ordering fixes the problem.

Automatic conversion from derived class to base class' member variable's type

Long story short: I'd like to understand why the D::operator B() const conversion operator is not used in the last line in the code below, which thus fails when compiling with g++ -std=c++17 source.cpp (compiling with g++ -std=c++2a deleteme.cpp is successful, though).
The error is:
$ g++ -std=c++17 deleteme.cpp && ./a.out
In file included from /usr/include/c++/10.2.0/cassert:44,
from deleteme.cpp:1:
deleteme.cpp: In function ‘int main()’:
deleteme.cpp:19:14: error: no match for ‘operator==’ (operand types are ‘D’ and ‘B’)
19 | assert(d == B{2}); // conversion operator not invoked explicitly errors // LINE
| ~ ^~ ~~~~
| | |
| D B
In file included from /usr/include/c++/10.2.0/utility:70,
from deleteme.cpp:2:
/usr/include/c++/10.2.0/bits/stl_pair.h:466:5: note: candidate: ‘template<class _T1, class _T2> constexpr bool std::operator==(const std::pair<_T1, _T2>&, const std::pair<_T1, _T2>&)’
466 | operator==(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
| ^~~~~~~~
/usr/include/c++/10.2.0/bits/stl_pair.h:466:5: note: template argument deduction/substitution failed:
In file included from /usr/include/c++/10.2.0/cassert:44,
from deleteme.cpp:1:
deleteme.cpp:19:20: note: ‘B’ is not derived from ‘const std::pair<_T1, _T2>’
19 | assert(d == B{2}); // conversion operator not invoked explicitly errors // LINE
|
The code is:
#include <cassert>
#include <utility>
struct B {
int x;
B(int x) : x(x) {}
bool operator==(B const& other) const { return x == other.x; }
};
struct D : std::pair<B,char*> {
operator B() const { return this->first; }
};
int main() {
B b{1};
D d{std::pair<B,char*>(B{2},(char*)"hello")};
assert((B)d == B{2}); // conversion operator invoked explicitly is fine
assert(d == B{2}); // conversion operator not invoked explicitly errors // LINE
}
This question is a follow up to this. There I got help to write a class Recursive which behaves like a pair (so inherits from it) whose first is a std::array and whose second is a boost::hana::optional<std::vector<...>> (see the link for details).
Since the second of the std::pair is kind of an "advanced" information to what's contained in the first, in many places I'd like to cast/convert this object of std::pair-like class Recursive to the type of its first.
When compiler sees d == B{2}, it first creates a list of operator== overloads that it's able to find, and then performs overload resolution on them.
As the link explains, the overload list contains:
Member operator==s of the first operand, if any.
Non-member operators==s found by unqualified lookup, if any.
Built-in operator==s, if your operands can be converted to built-in types.
There's no mention of examining conversion operators of the first operand, so your operator== doesn't get found.
The solution is to make the operator== non-member (possibly define it as a friend). This works:
friend bool operator==(const B &a, const B &b) {return a.x == b.x;}
Starting from C++20, the rules of comparison operators got relaxed: the compiler will look for member operator== in the second operand as well, and will happily call non-member operator== with arguments in a wrong order.
So a == b and b == a are now equivalent to a degree, except that:
An operator== called in this new manner must return bool.
If given choice, the compiler will prefer the old ways of calling operator== over calling the member operator== of the rhs, which is in turn preferred over calling a non-member operator== with a wrong argument order.

deleted friend declaration declares a non-template function

I want to delete a specific overloaded friend function in a class.
The below code compiles, and works in the way intended.
However, I get the following warning (g++, c++11,14,17):
edit: Warning is present using gcc9.3 and older, but not for gcc10
warning: friend declaration 'bool operator==(const MyType<BaseT>&, const BaseT&)' declares a non-template function [-Wnon-template-friend]
6 | friend bool operator==(const MyType<BaseT> &lhs, const BaseT &rhs) = delete;
| ^~~~~~
new.cpp:6:72: note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)
Can someone shed light on the warning? Why does it apply for the deleted function, but not the implemented one?
template <typename BaseT> struct MyType {
BaseT v;
explicit MyType(BaseT tv) : v(tv) {}
explicit operator BaseT() const { return v; }
friend bool operator==(const MyType<BaseT> &lhs, const BaseT &rhs) = delete;
friend bool operator==(const MyType<BaseT> &lhs, const BaseT &&rhs) {
return lhs.v == rhs;
}
};
int main(){
MyType<int> a{3};
int b{3};
// a==b; //Fails to compile: good. Do not allow comparisons between different types
a==3; //Compiles fine: good. Allow literal comparison, without implicit constructor
}

std::tuple member by member comparison fails

I wanted to test this very interesting answer and came out with this minimal implementation:
class A
{
enum M { a };
std::tuple<int> members;
public:
A() { std::get<M::a>(members) = 0; }
A(int value) { std::get<M::a>(members) = value; }
A(const A & other) { members = other.members; }
int get() const { return std::get<M::a>(members); }
bool operator==(A & other) { return members == other.members; }
};
and a simple test:
int main() {
A x(42);
A y(x);
std::cout << (x==y) << std::endl;
return 0;
}
Everything's fine, until I define a simple struct B {}; and try to add an instance of it as a member. As soon as I write
std::tuple<int, B> members;
the operator== isn't ok anymore and I get this message from the compiler (gcc 5.4.1):
error: no match for ‘operator==’ (operand types are ‘std::__tuple_element_t<1ul, std::tuple<int, B> > {aka const B}’ and ‘std::__tuple_element_t<1ul, std::tuple<int, B> > {aka const B}’)
return bool(std::get<__i>(__t) == std::get<__i>(__u))
^
I tried providing an operator== to B:
struct B
{
bool operator==(const B &){ return true; }
};
and had an extra from compiler:
candidate: bool B::operator==(const B&) <near match>
bool operator==(const B &){ return true; }
^
Can anyone explain what's wrong with B struct? Is it lacking something, or else?
It's ultimately const correctness. You didn't const qualify B's (or A's, for that matter) comparison operator and its parameter consistently.
Since tuple's operator== accepts by a const reference, it cannot use your const-incorrect implementation. And overload resolution fails as a consequence.
Tidying up all of those const qualifiers resolves all the errors.