Recursive spaceship operator - c++

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.

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.

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?

Is this a gcc 11 compiler bug with C++20 concepts, or am I doing something wrong?

I'm experimenting with C++20 concepts, using my own local build of a clone of the GCC 11 source code. I'm getting compilation errors from GCC that seem wrong to me. I've reduced my code triggering the diagnostic to what's below, with the GCC command line to compile it and the full resulting diagnostic output below that.
For focus, the particular section of the diagnostic output that seems wrong to me is
repro.cpp:9:13: note: the required expression ‘flequal(a, b)’ is invalid, because
9 | {flequal(a, b)} -> std::convertible_to<bool>;
| ~~~~~~~^~~~~~
repro.cpp:9:13: error: ‘flequal’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
repro.cpp:22:6: note: ‘template<class auto:1, class auto:2> requires (Flequalable<auto:1>) && (Flequalable<auto:2>) bool flequal(const Bar<auto:1>&, const Bar<auto:2>&)’ declared here, later in the translation unit
22 | bool flequal(Bar<Flequalable auto> const &a, Bar<Flequalable auto> const &b) {
| ^~~~~~~
It says the candidate function was declared later in the translation unit than the point of instantiation, but that is not true, at least going by source line order in my code snippet.
Is this build error wrong, or am I doing something wrong in my use of C++20 concepts?
Note the variant, in a comment at the end of the code, which compiles without any diagnostic.
Code:
#include <concepts>
using std::floating_point;
template< typename T >
concept Flequalable
= floating_point<T>
&& requires(T a, T b) {
{flequal(a, b)} -> std::convertible_to<bool>;
};
template<floating_point T>
bool flequal( T a, T b) {
return true;
}
template<typename T>
struct Bar {
T t;
};
bool flequal(Bar<Flequalable auto> const &a, Bar<Flequalable auto> const &b) {
return true;
}
bool foo() {
Bar<double> a = {2.0};
Bar<double> b = {3.0};
return flequal(a, b); // Causes diagnostic
// return flequal(a.t, b.t); // This works
}
Compilation command-line:
/usr/local/gcc-11-master/bin/g++-11 -c repro.cpp -o repro.o -std=c++20 -fconcepts-diagnostics-depth=5 2> diagnostic.txt
Full GCC diagnostic:
repro.cpp: In function ‘bool foo()’:
repro.cpp:29:24: error: no matching function for call to ‘flequal(Bar<double>&, Bar<double>&)’
29 | return flequal(a, b); // Causes diagnostic
| ^
repro.cpp:13:6: note: candidate: ‘template<class T> requires floating_point<T> bool flequal(T, T)’
13 | bool flequal( T a, T b) {
| ^~~~~~~
repro.cpp:13:6: note: template argument deduction/substitution failed:
repro.cpp:13:6: note: constraints not satisfied
In file included from repro.cpp:1:
/usr/local/gcc-11-master/include/c++/11.0.1/concepts: In substitution of ‘template<class T> requires floating_point<T> bool flequal(T, T) [with T = Bar<double>]’:
repro.cpp:29:24: required from here
/usr/local/gcc-11-master/include/c++/11.0.1/concepts:111:13: required for the satisfaction of ‘floating_point<T>’ [with T = Bar<double>]
/usr/local/gcc-11-master/include/c++/11.0.1/concepts:111:30: note: the expression ‘is_floating_point_v<_Tp> [with _Tp = Bar<double>]’ evaluated to ‘false’
111 | concept floating_point = is_floating_point_v<_Tp>;
| ^~~~~~~~~~~~~~~~~~~~~~~~
repro.cpp:22:6: note: candidate: ‘template<class auto:1, class auto:2> requires (Flequalable<auto:1>) && (Flequalable<auto:2>) bool flequal(const Bar<auto:1>&, const Bar<auto:2>&)’
22 | bool flequal(Bar<Flequalable auto> const &a, Bar<Flequalable auto> const &b) {
| ^~~~~~~
repro.cpp:22:6: note: template argument deduction/substitution failed:
repro.cpp:22:6: note: constraints not satisfied
repro.cpp: In substitution of ‘template<class auto:1, class auto:2> requires (Flequalable<auto:1>) && (Flequalable<auto:2>) bool flequal(const Bar<auto:1>&, const Bar<auto:2>&) [with auto:1 = double; auto:2 = double]’:
repro.cpp:29:24: required from here
repro.cpp:6:9: required for the satisfaction of ‘Flequalable<auto:1>’ [with auto:1 = double]
repro.cpp:8:8: in requirements with ‘T a’, ‘T b’ [with T = double]
repro.cpp:9:13: note: the required expression ‘flequal(a, b)’ is invalid, because
9 | {flequal(a, b)} -> std::convertible_to<bool>;
| ~~~~~~~^~~~~~
repro.cpp:9:13: error: ‘flequal’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
repro.cpp:22:6: note: ‘template<class auto:1, class auto:2> requires (Flequalable<auto:1>) && (Flequalable<auto:2>) bool flequal(const Bar<auto:1>&, const Bar<auto:2>&)’ declared here, later in the translation unit
22 | bool flequal(Bar<Flequalable auto> const &a, Bar<Flequalable auto> const &b) {
| ^~~~~~~
Here's the exact GCC version information:
Using built-in specs.
COLLECT_GCC=/usr/local/gcc-11-master/bin/g++-11
COLLECT_LTO_WRAPPER=/usr/local/gcc-11-master/libexec/gcc/x86_64-pc-linux-gnu/11.0.1/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: ../gcc/configure --prefix=/usr/local/gcc-11-master --program-suffix=-11 --enable-libstdcxx-debug : (reconfigured) ../gcc/configure --prefix=/usr/local/gcc-11-master --program-suffix=-11 --enable-libstdcxx-debug
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 11.0.1 20210304 (experimental) (GCC)
First, Bar<Flequalable auto> is not valid syntax. You can have a top-level variable declared like Concept auto v but you can't nest that within templates. So it really should be:
template <Flequalable T, Flequalable U>
bool flequal(Bar<T> const &a, Bar<U> const &b) {
return true;
}
Now, what happens when we try to call flequal(a, b). We deduce T=double and U=double and then try to evaluate if double satisfies Flequalable. double is floating_point, so we keep going to check the requirement that flequal(a, b) is a valid expression for two doubles.
This is unqualified lookup, so we first do regular unqualified lookup for the name flequal. It's important to keep in mind that this lookup happens from the point of the concept definition, not from the point where it is used. That is, we're looking up from here:
#include <concepts>
using std::floating_point;
template< typename T >
concept Flequalable
= floating_point<T>
&& requires(T a, T b) {
{flequal(a, b)} -> std::convertible_to<bool>; // <== here!
};
Not where the concept is used:
template <Flequalable T, Flequalable U> // <== not here
bool flequal(Bar<T> const &a, Bar<U> const &b) {
return true;
}
Nor where the function template using the concept is invoked:
return flequal(a, b); // <== not here either
We find nothing. There are no declarations of flequal visible from that point. Then, we do argument-dependent lookup. double has no associated namespaces, so this also trivially finds nothing. As such, there are zero candidates, so double does not satisfy Flequalable.
In order to make this work, you have to make sure that unqualified lookup in the concept Flequalable actually finds your flequal function. Just swap the two declarations, and then everything works fine.
This makes it seem like flequal is some kind of customization point, but there are only a fixed number of floating_point types (float, double, long double, and cv-qualified versions thereof) so I'm not entirely sure what the point here is.

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.

boost optional and user-defined conversion

I am unable to write a correct user defined conversion for a type Item. This is what I've tried:
#include <iostream>
#include <boost/optional.hpp>
struct A
{
int x;
};
struct Item
{
boost::optional<int> x_;
Item(){}
Item(const A& s)
: x_(s.x)
{
}
operator boost::optional<A>() const {
boost::optional<A> s;
if (x_) {
s->x = *x_;
}
return s;
}
};
std::vector<A> getA(const std::vector<Item> &items) {
std::vector<A> a;
for (const auto &i : items) {
if (i.x_) {
a.push_back(*static_cast<boost::optional<A>>(i)); // <- this line causes error
}
}
return a;
}
That is how I use it:
int main() {
A a;
a.x = 3;
Item i(a);
auto v = getA({i});
return 0;
}
g++ -std=c++11 says:
In file included from /usr/include/boost/optional.hpp:15:0,
from test.cpp:2:
/usr/include/boost/optional/optional.hpp: In instantiation of ‘void boost::optional_detail::optional_base<T>::construct(const Expr&, const void*) [with Expr = Item; T = A]’:
/usr/include/boost/optional/optional.hpp:262:25: required from ‘boost::optional_detail::optional_base<T>::optional_base(const Expr&, const Expr*) [with Expr = Item; T = A]’
/usr/include/boost/optional/optional.hpp:559:78: required from ‘boost::optional<T>::optional(const Expr&) [with Expr = Item; T = A]’
test.cpp:30:55: required from here
/usr/include/boost/optional/optional.hpp:392:8: error: no matching function for call to ‘A::A(const Item&)’
new (m_storage.address()) internal_type(expr) ;
^
/usr/include/boost/optional/optional.hpp:392:8: note: candidates are:
test.cpp:3:8: note: A::A()
struct A
^
test.cpp:3:8: note: candidate expects 0 arguments, 1 provided
test.cpp:3:8: note: constexpr A::A(const A&)
test.cpp:3:8: note: no known conversion for argument 1 from ‘const Item’ to ‘const A&’
test.cpp:3:8: note: constexpr A::A(A&&)
test.cpp:3:8: note: no known conversion for argument 1 from ‘const Item’ to ‘A&&’
Why does it try to find A struct constructor instead of use user defined conversion operator?
You may point me directly to any position of the user-defined conversion page because I am unable to find any reason for this. For example,
User-defined conversion function is invoked on the second stage of the implicit conversion, which consists of zero or one converting constructor or zero or one user-defined conversion function.
in my opinion directly says that if no conversion constructor is defined then user-defined conversion function will be used. Am I wrong? And if yes, how can I implement user-defined conversion then without defining conversion cunstructor in struct A ?
You have two issues with your code. Your optional operator never initializes the boost::optional. If you don't do that, accessing members is undefined behavior. What you have to do is:
operator boost::optional<A>() const {
boost::optional<A> s;
if (x_) {
s = A{*x_};
}
return s;
}
The second issue is when you do:
static_cast<boost::optional<A>>(i);
That is equivalent to:
boost::optional<A> __tmp(i);
But it turns out that boost::optional has an explicit template constructor. That will be preferred to your conversion function. The error you're seeing is the compiling going down the path of this factory constructor, where Item is not such a factory.
You could simply use boost::optional<A> directly:
std::vector<A> getA(const std::vector<Item> &items) {
std::vector<A> a;
for (boost::optional<A> opt : items) {
if (opt) {
a.push_back(*opt);
}
}
return a;
}
Or, since the constructor template is explicit, you could use the conversion operator in a non-explicit context:
boost::optional<A> opt = i;
a.push_back(*opt);
This has the added benefit of also being easier to read.