Inaccessible operator == breaks child class with spaceship operator - c++

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?

Related

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

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.

Avoiding need to specify using operator=

I'm attempting to create a base class for creating helpers around various scoped operations, and to do so the base class allows the assignment of a callable. A simple predicate determines whether or not the callable is invoked. Regardless, the dtor is always invoked.
The catch is that each derived class needs to explicitly inherit the operator=. Is there a way to write the base class so that this is not required?
#include <functional>
#include <iostream>
using callback_t = std::function<void(void)>;
struct C
{
constexpr C(bool ok) : ok_{ok} {}
bool ok_ {false};
bool operator=(callback_t fn) const noexcept {
return ok_ ? fn(), true : false;
}
~C() noexcept {
std::cout << "end\n";
}
};
struct D final : public C
{
using C::operator=;
};
int main()
{
auto predicate = [] { return true; };
D{predicate()} = [] {
std::cout << "ok\n";
};
D{false} = [] {
std::cout << "wrong\n";
};
}
(also https://gcc.godbolt.org/z/eh8ncffos)
Removing the line from struct D
using C::operator=;
causes compile errors since Ds default copy-operator preempts C's custom operator=.
<source>: In function 'int main()':
<source>:24:9: error: no match for 'operator=' (operand types are 'D' and 'main()::<lambda()>')
24 | };
| ^
<source>:14:12: note: candidate: 'constexpr D& D::operator=(const D&)'
14 | struct D final : public C
| ^
<source>:14:12: note: no known conversion for argument 1 from 'main()::<lambda()>' to 'const D&'
<source>:14:12: note: candidate: 'constexpr D& D::operator=(D&&)'
<source>:14:12: note: no known conversion for argument 1 from 'main()::<lambda()>' to 'D&&'
<source>:27:9: error: no match for 'operator=' (operand types are 'D' and 'main()::<lambda()>')
27 | };
| ^
<source>:14:12: note: candidate: 'constexpr D& D::operator=(const D&)'
14 | struct D final : public C
| ^
<source>:14:12: note: no known conversion for argument 1 from 'main()::<lambda()>' to 'const D&'
<source>:14:12: note: candidate: 'constexpr D& D::operator=(D&&)'
<source>:14:12: note: no known conversion for argument 1 from 'main()::<lambda()>' to 'D&&'
The catch is that each derived class needs to explicitly inherit the operator=. Is there a way to write the base class so that this is not required?
No, this isn't possible.
The rule is that functions in different scopes do not overload. Since the derived class will always have something named operator= (either the derived class declares one itself, or the copy assignment operator is declared implicitly... even if it would be defined as deleted). So when you do derived = x;, name lookup for operator= will start in Derived and, having found candidates, stop there. It won't keep going to look into the base class... unless you bring in that candidate with a using-declaration like you demonstrated the question.
The only way to still use operator= but stash the functionality somewhere would be to actually invert your class hierarchy. Instead of:
struct C {
// ... implementation here ...
};
struct D : C {
using C::operator=;
};
// use D
you can do:
template <typename Derived>
struct C {
// implementation here
};
struct D { }
// use C<D>
This way, C is actually the class you're interacting with, so its operator= would be the first candidate considered.
Alternatively, if you simply don't use = as the spelling for this operation, every other operator is available and they would not need a using-declaration in the derived class to work.
Given the comment about wishing to avoid parentheses, your list of binary operators that would not require one are: ->*, +, -, *, /, %, ^, &, |, &&, ||, <, >, <<, and >> (plus the compound-assignment versions of those).
I'm deliberately excluding == and <=> from the list because those are special and just don't.

User-defined conversion operator doesn't work on references

I have a simple primitive type wrapper:
template <typename T>
class Scalar {
public:
explicit Scalar(T value) : value{value} {}
Scalar(Scalar&& other) = default;
Scalar& operator=(Scalar&& other) = default;
Scalar(const Scalar& other) = default;
Scalar& operator=(const Scalar& other) = default;
template <typename U>
explicit operator Scalar<U>() {
return Scalar<U>{static_cast<U>(this->value)};
}
inline T getValue() const noexcept { return this->value; }
private:
T value;
};
Casting Scalar values works well, but somehow it fails for references, e.g.
auto a = Scalar<double>{2.54};
Scalar<int> b = static_cast<Scalar<int>>(a); // works
const auto& c = a;
Scalar<int> d = static_cast<Scalar<int>>(c); // fails
Here's the compilation error (can be checked here http://rextester.com/GOPYU13091), any ideas what might be the issue here?
source_file.cpp: In function ‘int main()’:
source_file.cpp:31:47: error: no matching function for call to ‘Scalar(const Scalar<double>&)’
Scalar<int> d = static_cast<Scalar<int>>(c);
^
source_file.cpp:12:3: note: candidate: Scalar<T>::Scalar(const Scalar<T>&) [with T = int] <near match>
Scalar(const Scalar& other) = default;
^
source_file.cpp:12:3: note: conversion of argument 1 would be ill-formed:
source_file.cpp:31:47: error: could not convert ‘c’ from ‘const Scalar<double>’ to ‘const Scalar<int>&’
Scalar<int> d = static_cast<Scalar<int>>(c);
^
source_file.cpp:10:3: note: candidate: Scalar<T>::Scalar(Scalar<T>&&) [with T = int] <near match>
Scalar(Scalar&& other) = default;
^
source_file.cpp:10:3: note: conversion of argument 1 would be ill-formed:
source_file.cpp:31:47: error: could not convert ‘c’ from ‘const Scalar<double>’ to ‘Scalar<int>&&’
Scalar<int> d = static_cast<Scalar<int>>(c);
^
This is a const vs non-const issue, not a reference vs object issue.
Using
auto& c = a;
Scalar<int> d = static_cast<Scalar<int>>(c);
works for me.
By changing the user defined conversion operator to const member function
template <typename U>
explicit operator Scalar<U>() const {
return Scalar<U>{static_cast<U>(this->value)};
}
also makes sure that the following works.
const auto& c = a;
Scalar<int> d = static_cast<Scalar<int>>(c);

Clash when using trivially copy/move-assignable union in constexpr

Considering the following code
struct S
{
constexpr S() = default;
constexpr S(S const &) = default;
constexpr S(S &) = default;
constexpr S(S &&) = default;
#if 1
S & operator = (S const &) = default;
S & operator = (S &) = default;
S & operator = (S &&) = default;
#else
constexpr S & operator = (S const &) = default;
constexpr S & operator = (S &) = default;
constexpr S & operator = (S &&) = default;
#endif
~S() = default;
};
struct U
{
union
{
S s;
};
constexpr U() : s{} { ; }
};
inline constexpr bool test()
{
U v;
U w;
v = w;
return true;
}
static_assert(test());
I found out that there is contradiction.
I want to use union (or euqally union-like class) in constexpr.
Version #if 1 gives an error (error: constexpr function never produces a constant expression):
main.cpp:31:23: error: constexpr function never produces a constant expression [-Winvalid-constexpr]
inline constexpr bool test()
^
main.cpp:35:7: note: non-constexpr function 'operator=' cannot be used in a constant expression
v = w;
^
main.cpp:19:8: note: declared here
struct U
^
main.cpp:39:15: error: static_assert expression is not an integral constant expression
static_assert(test());
^~~~~~
main.cpp:35:7: note: non-constexpr function 'operator=' cannot be used in a constant expression
v = w;
^
main.cpp:39:15: note: in call to 'test()'
static_assert(test());
^
main.cpp:19:8: note: declared here
struct U
^
2 errors generated.
But version #if 0 also give an error (error: defaulted definition of copy assignment operator is not constexpr):
main.cpp:12:5: error: defaulted definition of copy assignment operator is not constexpr
constexpr S & operator = (S const &) = default;
^
main.cpp:13:5: error: defaulted definition of copy assignment operator is not constexpr
constexpr S & operator = (S &) = default;
^
main.cpp:14:5: error: defaulted definition of move assignment operator is not constexpr
constexpr S & operator = (S &&) = default;
^
main.cpp:35:7: error: object of type 'U' cannot be assigned because its copy assignment operator is implicitly deleted
v = w;
^
main.cpp:24:11: note: copy assignment operator of 'U' is implicitly deleted because field 's' has no copy assignment operator
S s;
^
4 errors generated.
LIVE EXAMPLE
All above is for version of clang 3.7.0, but newer version clang 3.8.0 (trunk 253951) nothing says about constexpr S & operator = (S const &) = default; (it allows to compile such a code), but consequences are the same.
For trivial copy/move-constructors, but user-provided assignment operators analogous code (construction) compiles fine.
I think the problem (bug) is in user-declared special functions, but it should be legal at all.
Also please look at the last and last but one clauses of this code. Quite well-designed (at my mind) variant becomes useless in constexpr, due to difference in interpretation by compiler of next definitions:
struct S {};
and:
struct S
{
constexpr S() = default;
constexpr S(S const &) = default;
constexpr S(S &) = default;
constexpr S(S &&) = default;
S & operator = (S const &) = default;
S & operator = (S &) = default;
S & operator = (S &&) = default;
~S() = default;
};
But both is equivalent at my mind (the second recognized by compiler as not POD, but who cares?).
Why defaulted assignment operator of empty struct is not constexpr?
UPDATE:
Removing the declaration of destructor makes problem to disappear. So even user-declared (not user-provided) destructor make implicitly defined defaulted assignment operators to be not marked as constexpr and to be non-trivial. It definitely bug because no one source telling about this.

std::swap for non copiable but movable struct

According to C++ reference std::swap is equivalent to
T c(std::move(a)); a=std::move(b); b=std::move(c);
This should allow to swap two non copiable but movable object. Therefore I don't understand why
#include<utility>
struct Foo {
Foo() = delete;
Foo(int) {};
Foo(Foo &) = delete;
Foo(Foo &&) {};
~Foo() {};
};
int main() {
Foo a(1),b(2);
std::swap(a,b);
}
is refused by the compiler with
In file included from /usr/include/c++/4.8/bits/stl_pair.h:59:0,
from /usr/include/c++/4.8/utility:70,
from swap.cpp:1:
/usr/include/c++/4.8/bits/move.h: In instantiation of ‘void std::swap(_Tp&, _Tp&) [with _Tp = Foo]’:
swap.cpp:13:16: required from here
/usr/include/c++/4.8/bits/move.h:176:11: error: use of deleted function ‘Foo& Foo::operator=(const Foo&)’
__a = _GLIBCXX_MOVE(__b);
^
swap.cpp:3:8: note: ‘Foo& Foo::operator=(const Foo&)’ is implicitly declared as deleted because ‘Foo’ declares a move constructor or move assignment operator
struct Foo {
^
In file included from /usr/include/c++/4.8/bits/stl_pair.h:59:0,
from /usr/include/c++/4.8/utility:70,
from swap.cpp:1:
/usr/include/c++/4.8/bits/move.h:177:11: error: use of deleted function ‘Foo& Foo::operator=(const Foo&)’
__b = _GLIBCXX_MOVE(__tmp);
note: this is with GCC 4.8 and 4.9 but clang complain as well.
You declared a move constructor. However, you need a move assignment operator for std::swap. You should add the following two operators:
auto operator=(const Foo& rhs) & -> Foo& = delete;
auto operator=(Foo&& rhs) & noexcept -> Foo&
{
// ...
return *this;
}