I'm trying to understand the new default comparison operators introduced in C++20. My issue is about when an explicitly defaulted comparison operator gets implicitly defined. The following code example illustrates the question:
#include <iostream>
struct B
{
operator bool() const { return true; }
};
struct D : B
{
bool operator==(const D&) const = default;
};
bool operator==(B, B) { return false; }
int main ()
{ D d;
std::cout << (d == d);
}
/* Outputs:
0 in gcc 10.1
1 in msvc 19.26
*/
The output of this program is compiler-dependent. It seems that MSVC defines the operator== for class D when it is encounters the declaration as defaulted, hence it doesn't use the operator== that is defined later for class B. By contrast, gcc waits with the implicit definition of D's operator== until it is actually needed, by which time the operator== defined for B is in scope, and gets used. Which behavior, if either, is correct ?
A related question, is why a defaulted operator== can't be declared for a class with reference members ? I could see that reference members could pose a problem with the MSVC approach, because a reference member might refer to an incomplete type when the defaulting declaration for operator== is encountered. With the gcc approach, the reference's type would always be complete before gcc attempts to define the defaulted operator.
GCC is wrong here (not unsurprisingly considering its results). When creating the definition of a defaulted comparison operator, the standard says:
Name lookups in the defaulted definition of a comparison operator function are performed from a context equivalent to its function-body.
And the function body of a defaulted function is where = default is. Therefore, the bool operator==(B, B) function should not be visible to the function body of the defaulted comparison operator.
Related
The builtin operator-> is defined as (*p).m, which is just fine for my iterator, so overloading it would just waste my time and the maintainer's eyes.
Just trying it wouldn't guarantee portability, and I haven't been able to find an answer, though I fear that it is no, because apparently nobody has even considered it before.
Update:
I made a minimal test program to actually try this:
struct S { int m;};
struct P
{ auto& operator*() const { return s;}
auto operator->() const =default;// { return &s;}
S s;
};
int main()
{ P p;
p->m;
}
g++ (Debian 8.3.0-6) compiles this only without =default;//, so seems like defaulting or omitting the overload won't be portable for years at least.
Will built-in operator-> be used if I don't overload it?
No, only certain special member functions are implicitly declared for a given class-type(and that too under certain circumstances). And operator-> is not one of them. This can be seen from special members which states:
The six special members functions described above are members implicitly declared on classes under certain circumstances:
Default ctor
Dtor
Copy ctor
Copy assignment
Move ctor
Move assignment
(emphasis mine)
Note in the above list, there is no mention of operator->. This means that if you want to use -> with an object of your class-type then you must overload it explicitly.
Now, coming to your question about the error that you're getting.
compiles this only with the commented out definition, so seems like defaulting or omitting the overload won't be portable for years at least.
You're getting the error because operator-> cannot be defaulted. This can be seen from the same special members documentation which says:
each class can select explicitly which of these members exist with their default definition or which are deleted by using the keywords default and delete, respectively.
(emphasis mine)
Note the emphasis on "these members" above. In particular, only the six special members listed above can be defaulted. And again since operator-> is not one of them, it can't be defaulted using default.
As Anoop Rana pointed out, only special member functions can be defaulted, and, as Yksisarvinen said, the builtin operator-> exists only for builtin types.
Redundancy in overloaded operators is a long acknowledged problem.
Boost::Operators provides common overloads with CRTP,
including operator-> that mimics the builtin behavior:
#include <boost/operators.hpp>
struct S { int m;};
struct P : boost::dereferenceable< P, const S*>
{ auto& operator*() const { return s;}
S s;
};
int main()
{ P p;
p->m;
}
Unfortunately spelling out the return type is required.
(It shouldn't be required IMHO.)
Alone it isn't a big step forward, but
it's bundled in commonly needed groups like input_iteratable.
I'm running into a strange behavior with the new spaceship operator <=> in C++20. I'm using Visual Studio 2019 compiler with /std:c++latest.
This code compiles fine, as expected:
#include <compare>
struct X
{
int Dummy = 0;
auto operator<=>(const X&) const = default; // Default implementation
};
int main()
{
X a, b;
a == b; // OK!
return 0;
}
However, if I change X to this:
struct X
{
int Dummy = 0;
auto operator<=>(const X& other) const
{
return Dummy <=> other.Dummy;
}
};
I get the following compiler error:
error C2676: binary '==': 'X' does not define this operator or a conversion to a type acceptable to the predefined operator
I tried this on clang as well, and I get similar behavior.
I would appreciate some explanation on why the default implementation generates operator== correctly, but the custom one doesn't.
This is by design.
[class.compare.default] (emphasis mine)
3 If the class definition does not explicitly declare an ==
operator function, but declares a defaulted three-way comparison
operator function, an == operator function is declared implicitly
with the same access as the three-way comparison operator function.
The implicitly-declared == operator for a class X is an inline
member and is defined as defaulted in the definition of X.
Only a defaulted <=> allows a synthesized == to exist. The rationale is that classes like std::vector should not use a non-defaulted <=> for equality tests. Using <=> for == is not the most efficient way to compare vectors. <=> must give the exact ordering, whereas == may bail early by comparing sizes first.
If a class does something special in its three-way comparison, it will likely need to do something special in its ==. Thus, instead of generating a potentially non-sensible default, the language leaves it up to the programmer.
During the standardization of this feature, it was decided that equality and ordering should logically be separated. As such, uses of equality testing (== and !=) will never invoke operator<=>. However, it was still seen as useful to be able to default both of them with a single declaration. So if you default operator<=>, it was decided that you also meant to default operator== (unless you define it later or had defined it earlier).
As to why this decision was made, the basic reasoning goes like this. Consider std::string. Ordering of two strings is lexicographical; each character has its integer value compared against each character in the other string. The first inequality results in the result of ordering.
However, equality testing of strings has a short-circuit. If the two strings aren't of equal length, then there's no point in doing character-wise comparison at all; they aren't equal. So if someone is doing equality testing, you don't want to do it long-form if you can short-circuit it.
It turns out that many types that need a user-defined ordering will also offer some short-circuit mechanism for equality testing. To prevent people from implementing only operator<=> and throwing away potential performance, we effectively force everyone to do both.
The other answers explain really well why the language is like this. I just wanted to add that in case it's not obvious, it is of course possible to have a user-provided operator<=> with a defaulted operator==. You just need to explicitly write the defaulted operator==:
struct X
{
int Dummy = 0;
auto operator<=>(const X& other) const
{
return Dummy <=> other.Dummy;
}
bool operator==(const X& other) const = default;
};
Note that the defaulted operator== performs memberwise == comparisons. That is to say, it is not implemented in terms of the user-provided operator<=>. So requiring the programmer to explicitly ask for this is a minor safety feature to help prevent surprises.
I'm running into a strange behavior with the new spaceship operator <=> in C++20. I'm using Visual Studio 2019 compiler with /std:c++latest.
This code compiles fine, as expected:
#include <compare>
struct X
{
int Dummy = 0;
auto operator<=>(const X&) const = default; // Default implementation
};
int main()
{
X a, b;
a == b; // OK!
return 0;
}
However, if I change X to this:
struct X
{
int Dummy = 0;
auto operator<=>(const X& other) const
{
return Dummy <=> other.Dummy;
}
};
I get the following compiler error:
error C2676: binary '==': 'X' does not define this operator or a conversion to a type acceptable to the predefined operator
I tried this on clang as well, and I get similar behavior.
I would appreciate some explanation on why the default implementation generates operator== correctly, but the custom one doesn't.
This is by design.
[class.compare.default] (emphasis mine)
3 If the class definition does not explicitly declare an ==
operator function, but declares a defaulted three-way comparison
operator function, an == operator function is declared implicitly
with the same access as the three-way comparison operator function.
The implicitly-declared == operator for a class X is an inline
member and is defined as defaulted in the definition of X.
Only a defaulted <=> allows a synthesized == to exist. The rationale is that classes like std::vector should not use a non-defaulted <=> for equality tests. Using <=> for == is not the most efficient way to compare vectors. <=> must give the exact ordering, whereas == may bail early by comparing sizes first.
If a class does something special in its three-way comparison, it will likely need to do something special in its ==. Thus, instead of generating a potentially non-sensible default, the language leaves it up to the programmer.
During the standardization of this feature, it was decided that equality and ordering should logically be separated. As such, uses of equality testing (== and !=) will never invoke operator<=>. However, it was still seen as useful to be able to default both of them with a single declaration. So if you default operator<=>, it was decided that you also meant to default operator== (unless you define it later or had defined it earlier).
As to why this decision was made, the basic reasoning goes like this. Consider std::string. Ordering of two strings is lexicographical; each character has its integer value compared against each character in the other string. The first inequality results in the result of ordering.
However, equality testing of strings has a short-circuit. If the two strings aren't of equal length, then there's no point in doing character-wise comparison at all; they aren't equal. So if someone is doing equality testing, you don't want to do it long-form if you can short-circuit it.
It turns out that many types that need a user-defined ordering will also offer some short-circuit mechanism for equality testing. To prevent people from implementing only operator<=> and throwing away potential performance, we effectively force everyone to do both.
The other answers explain really well why the language is like this. I just wanted to add that in case it's not obvious, it is of course possible to have a user-provided operator<=> with a defaulted operator==. You just need to explicitly write the defaulted operator==:
struct X
{
int Dummy = 0;
auto operator<=>(const X& other) const
{
return Dummy <=> other.Dummy;
}
bool operator==(const X& other) const = default;
};
Note that the defaulted operator== performs memberwise == comparisons. That is to say, it is not implemented in terms of the user-provided operator<=>. So requiring the programmer to explicitly ask for this is a minor safety feature to help prevent surprises.
I'm running into a strange behavior with the new spaceship operator <=> in C++20. I'm using Visual Studio 2019 compiler with /std:c++latest.
This code compiles fine, as expected:
#include <compare>
struct X
{
int Dummy = 0;
auto operator<=>(const X&) const = default; // Default implementation
};
int main()
{
X a, b;
a == b; // OK!
return 0;
}
However, if I change X to this:
struct X
{
int Dummy = 0;
auto operator<=>(const X& other) const
{
return Dummy <=> other.Dummy;
}
};
I get the following compiler error:
error C2676: binary '==': 'X' does not define this operator or a conversion to a type acceptable to the predefined operator
I tried this on clang as well, and I get similar behavior.
I would appreciate some explanation on why the default implementation generates operator== correctly, but the custom one doesn't.
This is by design.
[class.compare.default] (emphasis mine)
3 If the class definition does not explicitly declare an ==
operator function, but declares a defaulted three-way comparison
operator function, an == operator function is declared implicitly
with the same access as the three-way comparison operator function.
The implicitly-declared == operator for a class X is an inline
member and is defined as defaulted in the definition of X.
Only a defaulted <=> allows a synthesized == to exist. The rationale is that classes like std::vector should not use a non-defaulted <=> for equality tests. Using <=> for == is not the most efficient way to compare vectors. <=> must give the exact ordering, whereas == may bail early by comparing sizes first.
If a class does something special in its three-way comparison, it will likely need to do something special in its ==. Thus, instead of generating a potentially non-sensible default, the language leaves it up to the programmer.
During the standardization of this feature, it was decided that equality and ordering should logically be separated. As such, uses of equality testing (== and !=) will never invoke operator<=>. However, it was still seen as useful to be able to default both of them with a single declaration. So if you default operator<=>, it was decided that you also meant to default operator== (unless you define it later or had defined it earlier).
As to why this decision was made, the basic reasoning goes like this. Consider std::string. Ordering of two strings is lexicographical; each character has its integer value compared against each character in the other string. The first inequality results in the result of ordering.
However, equality testing of strings has a short-circuit. If the two strings aren't of equal length, then there's no point in doing character-wise comparison at all; they aren't equal. So if someone is doing equality testing, you don't want to do it long-form if you can short-circuit it.
It turns out that many types that need a user-defined ordering will also offer some short-circuit mechanism for equality testing. To prevent people from implementing only operator<=> and throwing away potential performance, we effectively force everyone to do both.
The other answers explain really well why the language is like this. I just wanted to add that in case it's not obvious, it is of course possible to have a user-provided operator<=> with a defaulted operator==. You just need to explicitly write the defaulted operator==:
struct X
{
int Dummy = 0;
auto operator<=>(const X& other) const
{
return Dummy <=> other.Dummy;
}
bool operator==(const X& other) const = default;
};
Note that the defaulted operator== performs memberwise == comparisons. That is to say, it is not implemented in terms of the user-provided operator<=>. So requiring the programmer to explicitly ask for this is a minor safety feature to help prevent surprises.
One of the weirder corner cases of C is that functions can be declared within other functions, e.g.
void foo(void)
{
void bar(void); // Behaves as if this was written above void foo(void)
bar();
}
This has carried through into C++, at least for most functions. Clang doesn't appear to recognise the pattern if the function in question happens to be called operator==.
struct foo
{
int value;
};
struct bar
{
foo value;
};
bool wot(const bar &x, const bar &y)
{
bool eq(const foo &, const foo &); // Declare function eq
bool operator==(const foo &, const foo &); // Declare function operator==
bool func = eq(x.value, y.value); // This line compiles fine
bool call = operator==(x.value, y.value); // Also OK - thanks user657267!
bool op = x.value == y.value; // This one doesn't
return func && call && op;
}
bool test()
{
bar a;
bar b;
return wot(a,b);
}
GCC and ICC compile this fine. Checking name mangling in the object suggests the operator== has been declared with the right types. Clang (I tried up to 3.8) errors:
error: invalid operands to binary expression
('const foo' and 'const foo')
bool op = x.value == y.value;
~~~~~~~ ^ ~~~~~~~
Unless the declaration is moved to directly above the function, in which case Clang is happy too:
bool operator==(const foo &, const foo &);
bool wot(const bar &x, const bar &y)
{
return x.value == y.value; // fine
}
I can't use this workaround as the "real world" case that provoked this question involves layers of templates, meaning I only know the type name "foo" within the function declaration.
I believe this is a bug in Clang - is there special handling of operatorX free functions which prohibits declaring them within a function?
For overloaded operators, see [over.match.oper]/(3.2):
[…] for a binary operator # with a left operand of a type whose cv-unqualified version is T1 and a right operand of a type whose
cv-unqualified version is T2, […] non-member
candidates […] are constructed as follows:
The set of non-member candidates is the result of the unqualified
lookup of operator# in the context of the expression according to the
usual rules for name lookup in unqualified function calls (3.4.2)
except that all member functions are ignored. However, if no operand
has a class type, […]
That is, we have the exact same name lookup rules as in ordinary calls, because x.value is of class type (foo). It's a Clang bug, and it occurs with all binary operators. Filed as #27027.
It is true that C allows functions to be declared inside functions: 6.7.5.3 Function declarators §17 (draft n1256 for C99) says (emphasize mine)
If the declaration occurs outside of any function, the identifiers have file scope and external linkage. If the
declaration occurs inside a function, the identifiers of the functions f and fip have block scope and either
internal or external linkage (depending on what file scope declarations for these identifiers are visible), and
the identifier of the pointer pfi has block scope and no linkage.
C++ also allows them. Draft n4296 for C++ 14 says:
13.2 Declaration matching [over.dcl] ...
2 A locally declared function is not in the same scope as a function in a containing scope. [ Example:
void f(const char*);
void g() {
extern void f(int);
...
(the above quote is only here to have an explicit evidence that C++ allows function declarations inside function)
I could even test that with your example this line:
bool op2 = operator == (x.value, y.value);
compiles fine without a single warning and gives expected results.
So I would say that it is a bug in Clang