Overloading operator== with `&&` and `const` qualifier cause ambiguity in C++20 - c++

Consider the struct S with two operator== overloads of same && qualifier and different const qualifier:
struct S {
bool operator==(const S&) && {
return true;
}
bool operator==(const S&) const && {
return true;
}
};
If I compare the two S with operator==:
S{} == S{};
gcc and msvc accept this code, clang rejects it with:
<source>:14:7: error: use of overloaded operator '==' is ambiguous (with operand types 'S' and 'S')
S{} == S{};
~~~ ^ ~~~
Why does clang think there is an ambiguous overload resolution here? Shouldn't the non-const one be the best candidate in this case?
Similarly, if I compare two S with the synthesized operator!=:
S{} != S{};
gcc still accept this code, but msvc and clang doesn't:
<source>:14:7: error: use of overloaded operator '!=' is ambiguous (with operand types 'S' and 'S')
S{} != S{};
~~~ ^ ~~~
It seems weird that the synthesized operator!= suddenly causes the ambiguity for msvc. Which compiler is right?

The example would be unambiguous in C++17. C++20 brings change:
[over.match.oper]
For a unary operator # with an operand of type cv1 T1, and for a binary operator # with a left operand of type cv1 T1 and a right operand of type cv2 T2, four sets of candidate functions, designated member candidates, non-member candidates, built-in candidates, and rewritten candidates, are constructed as follows:
...
For the operator ,, the unary operator &, or the operator ->, the built-in candidates set is empty. For all other operators, the built-in candidates include all of the candidate operator functions defined in [over.built] that, compared to the given operator,
have the same operator name, and
accept the same number of operands, and
accept operand types to which the given operand or operands can be converted according to [over.best.ics], and
do not have the same parameter-type-list as any non-member candidate that is not a function template specialization.
The rewritten candidate set is determined as follows:
...
For the equality operators, the rewritten candidates also include a synthesized candidate, with the order of the two parameters reversed, for each non-rewritten candidate for the expression y == x.
Thus, the rewritten candidate set includes these:
implicit object parameter
|||
(S&&, const S&); // 1
(const S&&, const S&); // 2
// candidates that match with reversed arguments
(const S&, S&&); // 1 reversed
(const S&, const S&&); // 2 reversed
The overload 1 is better match than 2, but the synthesised reversed overload of 1 is ambiguous with the original non-reversed overload because both have const conversion to one parameter. Note that this is actually ambiguous even if overload 2 doesn't exist.
Thus, Clang is correct.
This is also covered by the informative compatibility annex:
Affected subclause: [over.match.oper] Change: Equality and inequality expressions can now find reversed and rewritten candidates.
Rationale: Improve consistency of equality with three-way comparison and make it easier to write the full complement of equality
operations.
Effect on original feature: Equality and inequality expressions between two objects of different types, where one is convertible to
the other, could invoke a different operator. Equality and inequality
expressions between two objects of the same type could become
ambiguous.
struct A {
operator int() const;
};
bool operator==(A, int); // #1
// #2 is built-in candidate: bool operator==(int, int);
// #3 is built-in candidate: bool operator!=(int, int);
int check(A x, A y) {
return (x == y) + // ill-formed; previously well-formed
(10 == x) + // calls #1, previously selected #2
(10 != x); // calls #1, previously selected #3
}

Related

templated operator "+" overloading and enum initialization [constexpr][msvc]

Please, consider the following code:
template<typename T>
T operator+(const T& lvh, const T& rvh)
{
return lvh;
}
struct enum_container {
enum {
item1 = 1,
item2 = 2,
item3 = item1 + item2
};
};
int main() {
return 0;
}
Compiling with MS VS 2022 gives the following error:
error C2131: expression did not evaluate to a constant
message : failure was caused by call of undefined function or one not declared 'constexpr'
message : see usage of 'operator +'
The problem applies to the third element in enum.
Why cl tries to substitute template for the constant expression?
Compilation with gcc gives no errors.
I can eliminate the error with cl adding type specifier to the enum, like this:
enum : int {
...
}
But I can`t do this in the project I work with. Both enum and overload declared by 3rd party headers. All I can do is to change includ order. But I'm still wondering why it works that way...
To me it looks like MSVC is correct here.
First, because template argument deduction should happen before overload resolution to form a set of candidate functions:
Before overload resolution begins, the functions selected by name
lookup and template argument deduction are combined to form the set of
candidate functions
And then, since the non-member candidates (in this case template specialisation of the operator+ overload with the enum_container::<anonymous_enum> parameters) precede built-in candidates (conversion to the underlying built-in type, for which a user-defined overload would not be possible), the compiler is meant to resolve the overload to the instance of the template with the user-defined enum-type:
non-member candidates: For the operators where operator overloading
permits non-member forms, all declarations found by unqualified name
lookup of operator# in the context of the expression (which may
involve ADL), except that member function declarations are ignored and
do not prevent the lookup from continuing into the next enclosing
scope. If both operands of a binary operator or the only operand of a
unary operator has enumeration type, the only functions from the
lookup set that become non-member candidates are the ones whose
parameter has that enumeration type (or reference to that enumeration
type)
built-in candidates: For operator,, the unary operator&, and the operator->, the set of built-in candidates is empty. For other
operators built-in candidates are the ones listed in built-in operator
pages as long as all operands can be implicitly converted to their
parameters. If any built-in candidate has the same parameter list as a
non-member candidate that isn't a function template specialization, it
is not added to the list of built-in candidates. When the built-in
assignment operators are considered, the conversions from their
left-hand arguments are restricted: user-defined conversions are not
considered.
Thus, the fact that GCC/Clang ignore the existence of the user-defined overload of operator+ under the enum definition doesn't seem to be compliant with the rules listed above. The same expression inside of a block scope causes exactly same error for all compilers (and I couldn't find any rationale why this might be different for the scope of enum definition):
struct enum_container {
enum {
item1 = 1,
item2 = 2,
item3 = item1 + item2
};
};
template<typename T>
T operator+(const T& lhs, const T&) {
return lhs;
}
int main() {
// error: call to non-'constexpr' function 'T operator+(const T&, const T&)
constexpr auto enumval = enum_container::item1 + enum_container::item2;
return 0;
}

Is user-defined conversion to fundamental-type deletable?

template<typename Integral>
struct IntegralWrapper {
Integral _value;
IntegralWrapper() = default;
IntegralWrapper(Integral value)
: _value(value) {}
operator Integral() const {
return _value;
}
operator bool() const = delete;
};
int main() {
IntegralWrapper<int> i1, i2;
i1 * i2;
}
It's compiled successfully by gcc, but failed by MSVC and clang, with error overloaded operator '*' is ambiguous. The problem comes from the explicit deleted operator bool.
https://godbolt.org/z/nh6M11d98
Which side (gcc or clang/MSVC) is right? And why?
First of all: Deleting a function does not prevent it from being considered in overload resolution (with some minor exceptions not relevant here). The only effect of = delete is that the program will be ill-formed if the conversion function is chosen by overload resolution.
For the overload resolution:
There are candidate built-in overloads for the * operator for all pairs of promoted arithmetic types.
So, instead of using * we could also consider
auto mul(int a, int b) { return a*b; } // (1)
auto mul(long a, long b) { return a*b; } // (2)
// further overloads, also with non-matching parameter types
mul(i1, i2);
Notably there are no overloads including bool, since bool is promoted to int.
For (1) the chosen conversion function for both arguments is operator int() const instantiated from operator Integral() const since conversion from int to int is better than bool to int. (Or at least that seems to be the intent, see e.g. https://github.com/cplusplus/draft/issues/2288 and In overload resolution, does selection of a function that uses the ambiguous conversion sequence necessarily result in the call being ill-formed?).
For (2) however, neither conversion from int or bool to long is better than the other. As a result the implicit conversion sequences will for the purpose of overload resolution be the ambiguous conversion sequence. This conversion sequence is considered distinct from all other user-defined conversion sequences.
When then comparing which of the overloads is the better one, neither can be considered better than the other, because both use user-defined conversion sequences for both parameters, but the used conversion sequences are not comparable.
As a result overload resolution should fail. If I completed the list of built-in operator overloads I started above, nothing would change. The same logic applies to all of them.
So MSVC and Clang are correct to reject and GCC is wrong to accept. Interestingly with the explicit example of functions I gave above GCC does reject as expected.
To disallow implicit conversions to bool you could use a constrained conversion function template, which will not allow for another standard conversion sequence after the user-defined conversion:
template<std::same_as<int> T>
operator T() const { return _value; }
This will allow only conversions to int. If you can't use C++20, you will need to replace the concept with SFINAE via std::enable_if.

Conversion operator: gcc vs clang

Consider the following code (https://godbolt.org/z/s17aoczj6):
template<class T>
class Wrapper {
public:
explicit Wrapper(T t): _value(t) {}
template<class S = T>
operator T() { return _value; }
private:
T _value;
};
auto main() -> int
{
auto i = int{0};
auto x = Wrapper<int>(i);
return x + i;
}
It compiles with clang, but not with gcc (all versions).
It works in gcc when removing the template<class S = T>.
Is this code ill-formed or is one compiler wrong?
The error is in gcc is error: no match for 'operator+' (operand types are 'Wrapper<int>' and 'int') return x + i;.
I want a conversion to T. The template is not necessary in this example, but in a non-minimal example I would like to use SFINAE and therefore need a template here.
When you have x + i, since x is of class type, overload resolution kicks in:
The specific details from the standard ([over.match.oper]p2)
If either operand has a type that is a class or an enumeration, a user-defined operator function might be declared that implements this operator or a user-defined conversion can be necessary to convert the operand to a type that is appropriate for a built-in operator.
In this case, overload resolution is used to determine which operator function or built-in operator is to be invoked to implement the operator.
The built-in candidates are defined in paragraph 3.3:
For the operator ,, the unary operator &, or the operator ->, the built-in candidates set is empty. For all other operators, the built-in candidates include all of the candidate operator functions defined in [over.built] that, compared to the given operator,
have the same operator name, and
accept the same number of operands, and
accept operand types to which the given operand or operands can be converted according to [over.best.ics], and
do not have the same parameter-type-list as any non-member candidate that is not a function template specialization.
The built-in candidate functions could consist of, according to [over.built]p13:
For every pair of types L and R, where each of L and R is a floating-point or promoted integral type, there exist candidate operator functions of the form
LR operator*(L, R);
...
LR operator+(L, R);
...
bool operator>=(L, R);
where LR is the result of the usual arithmetic conversions ([expr.arith.conv]) between types L and R.
So there is a built-in function int operator+(int, int).
As to what possible implicit conversion sequences there are:
[over.best.ics]p3:
A well-formed implicit conversion sequence is one of the following forms:
a standard conversion sequence,
a user-defined conversion sequence, or
an ellipsis conversion sequence.
And here a user-defined conversion sequence is used, defined by [over.ics.user]:
A user-defined conversion sequence consists of an initial standard conversion sequence followed by a user-defined conversion ([class.conv]) followed by a second standard conversion sequence.
(Here, both standard conversion sequences are empty, and your user defined conversion to an int can be used)
So when checking if int operator+(int, int) is a built-in candidate, it is since there exists a conversion between your class type and int.
As for the actual overload resolution, from [over.match.oper]:
The set of candidate functions for overload resolution for some operator # is the union of the member candidates, the non-member candidates, the built-in candidates, and the rewritten candidates for that operator #.
The argument list contains all of the operands of the operator.
The best function from the set of candidate functions is selected according to [over.match.viable] and [over.match.best].
And int operator+(int, int) is obviously the best match, since it requires no conversion for the second argument and only a user defined conversion for the first, so it beats other candidates like long operator+(long, int) and long operator+(int, long)
You can see the problem that the built-in candidate set is empty, since the GCC error reports that there are no viable candidates. If you instead had:
auto add(int a, int b) -> int
{
return a + b;
}
auto main() -> int
{
auto i = int{0};
auto x = Wrapper<int>(i);
return add(x, i);
}
it now compiles fine with GCC since ::add(int, int) is considered a candidate, even though it should be no different from the built-in operator int operator+(int, int).
If you instead had:
template<class S = T>
operator S() { return _value; } // Can convert to any type
Clang now has the error:
<source>:16:14: error: use of overloaded operator '+' is ambiguous (with operand types 'Wrapper<int>' and 'int')
return x + i;
~ ^ ~
<source>:16:14: note: built-in candidate operator+(float, int)
<source>:16:14: note: built-in candidate operator+(double, int)
<source>:16:14: note: built-in candidate operator+(long double, int)
<source>:16:14: note: built-in candidate operator+(__float128, int)
<source>:16:14: note: built-in candidate operator+(int, int)
<source>:16:14: note: built-in candidate operator+(long, int)
<source>:16:14: note: built-in candidate operator+(long long, int)
<source>:16:14: note: built-in candidate operator+(__int128, int)
<source>:16:14: note: built-in candidate operator+(unsigned int, int)
<source>:16:14: note: built-in candidate operator+(unsigned long, int)
<source>:16:14: note: built-in candidate operator+(unsigned long long, int)
<source>:16:14: note: built-in candidate operator+(unsigned __int128, int)
(Note this error message excludes conversions of the second argument, but since these will never be chosen, they are probably not considered as an optimisation)
And GCC still says there are no candidates at all, even though all of these built-in candidates exist.

C++ "error: use of overloaded operator '*' is ambiguous" with seemingly only one match

I am trying to use custom operators in C++ for a project I am working on. This project uses the ROCm/HIP stack (so, under the hood, the clang compiler).
Here's the error message:
src/zlatrd.cpp:359:32: error: use of overloaded operator '*' is ambiguous (with operand types 'magmaDoubleComplex' (aka 'hip_complex_number<double>') and 'float')
alpha = tau[i] * -0.5f * value;
~~~~~~ ^ ~~~~~
./include/magma_operators.h:190:1: note: candidate function
operator * (const magmaDoubleComplex a, const double s)
^
./include/magma_operators.h:183:1: note: candidate function
operator * (const magmaDoubleComplex a, const magmaDoubleComplex b)
^
./include/magma_operators.h:437:1: note: candidate function
operator * (const magmaFloatComplex a, const float s)
^
./include/magma_operators.h:430:1: note: candidate function
operator * (const magmaFloatComplex a, const magmaFloatComplex b)
^
It seems to me that it is not ambiguous; it should select the third candidate function, as the argument is a float.
Here is the type definition for the hip_complex_number template:
template <typename T>
struct hip_complex_number
{
T x, y;
template <typename U>
hip_complex_number(U a, U b)
: x(a)
, y(b)
{
}
template <typename U>
hip_complex_number(U a)
: x(a)
, y(0)
{
}
hip_complex_number()
: x(0)
, y(0)
{
}
};
I notice it has an implicit constructor that will convert a float, but I assumed that given a candidate function that matches the type exactly (not including the const modifier), that it would obviously select that function overload over those which require an implicit cast.
EDIT: Also, I know that by default C/C++ convert from float/double to each other if the function is defined in that way, so 'matches the type exactly' was definitely not the right wording.
Can someone explain why C++ thinks this is ambiguous?
EDIT: People have asked for the definition of magmaFloatComplex, which is hip_complex_number<float>
Please note that I don't know anything about the library that these types are from. I will explain the ambiguity purely based on the information in the question.
The first and third overload are ambiguous.
In the overload operator * (const magmaDoubleComplex a, const double s) a floating-point promotion from float to double is required in the second argument.
In the overload operator * (const magmaFloatComplex a, const float s) a user-defined conversion to an unrelated type from magmaDoubleComplex to magmaFloatComplex is required. This conversion is possible, because of the non-explicit converting constructor
template <typename U>
hip_complex_number(U a)
The corresponding other parameters don't need any conversion aside from potentially lvalue-to-rvalue conversions or user-defined conversion to the same type, which are considered exact match.
Exact match is better than either user-defined conversion to an unrelated type or floating-point promotion, meaning that each overload has one parameter that is better than the other one's and one that is worse than the other one's.
Overload resolution is ambiguous if not at least one overload has all parameters not worse than all other overload's parameters. Therefore the two mentioned overloads are ambiguous here.
The second overload has exact match in the first argument and requires a user-defined conversion to unrelated type in the second argument, which is again possible because of the converting constructor mentioned above. However the floating-point promotion of the first overload is considered better than a user-defined conversion to unrelated type and therefore the second overload looses against the first one in overload resolution, but would be ambiguous with the third one as well.
The fourth overload is worse than all the others, because it requires user-defined conversions to unrelated types in both parameters.
Note that if overload 3 would be selected as expect in your question, it would result in an error, because the converting constructor chosen for magmaFloatComplex will try to initialize the x member which is of type float with a magmaDoubleComplex, which (at least based on your shown code) doesn't have a conversion operator to float.

Why is this C++ expression involving overloaded operators and implicit conversions ambiguous?

operator bool breaks the use of operator< in the following example. Can anyone explain why bool is just as relevant in the if (a < 0) expression as the specific operator, an whether there is a workaround?
struct Foo {
Foo() {}
Foo(int x) {}
operator bool() const { return false; }
friend bool operator<(const Foo& a, const Foo& b) {
return true;
}
};
int main() {
Foo a, b;
if (a < 0) {
a = 0;
}
return 1;
}
When I compile, I get:
g++ foo.cpp
foo.cpp: In function 'int main()':
foo.cpp:18:11: error: ambiguous overload for 'operator<' (operand types are 'Foo' and 'int')
if (a < 0) {
^
foo.cpp:18:11: note: candidate: operator<(int, int) <built-in>
foo.cpp:8:17: note: candidate: bool operator<(const Foo&, const Foo&)
friend bool operator<(const Foo& a, const Foo& b)
The problem here is that C++ has two options to deal with a < 0 expression:
Convert a to bool, and compare the result to 0 with built-in operator < (one conversion)
Convert 0 to Foo, and compare the results with < that you defined (one conversion)
Both approaches are equivalent to the compiler, so it issues an error.
You can make this explicit by removing the conversion in the second case:
if (a < Foo(0)) {
...
}
The important points are:
First, there are two relevant overloads of operator <.
operator <(const Foo&, const Foo&). Using this overload requires a user-defined conversion of the literal 0 to Foo using Foo(int).
operator <(int, int). Using this overload requires converting Foo to bool with the user-defined operator bool(), followed by a promotion to int (this is, in standardese, different from a conversion, as has been pointed out by Bo Persson).
The question here is: From whence does the ambiguity arise? Certainly, the first call, which requires only a user-defined conversion, is more sensible than the second, which requires a user-defined conversion followed by a promotion?
But that is not the case. The standard assigns a rank to each candidate. However, there is no rank for "user-defined conversion followed by a promotion". This has the same rank as only using a user-defined conversion. Simply (but informally) put, the ranking sequence looks a bit like this:
exact match
(only) promotion required
(only) implicit conversion required (including "unsafe" ones inherited from C such as float to int)
user-defined conversion required
(Disclaimer: As mentioned, this is informal. It gets significantly more complex when multiple arguments are involved, and I also didn't mention references or cv-qualification. This is just intended as a rough overview.)
So this, hopefully, explains why the call is ambiguous. Now for the practical part of how to fix this. Almost never does someone who provides operator bool() want it to be implicitly used in expressions involving integer arithmetic or comparisons. In C++98, there were obscure workarounds, ranging from std::basic_ios<CharT, Traits>::operator void * to "improved" safer versions involving pointers to members or incomplete private types. Fortunately, C++11 introduced a more readable and consistent way of preventing integer promotion after implicit uses of operator bool(), which is to mark the operator as explicit. This will remove the operator <(int, int) overload entirely, rather than just "demoting" it.
As others have mentioned, you can also mark the Foo(int) constructor as explicit. This will have the converse effect of removing the operator <(const Foo&, const Foo&) overload.
A third solution would be to provide additional overloads, e.g.:
operator <(int, const Foo&)
operator <(const Foo&, int)
The latter, in this example, will then be preferred over the above-mentioned overloads as an exact match, even if you did not introduce explicit. The same goes e.g. for
operator <(const Foo&, long long)
which would be preferred over operator <(const Foo&, const Foo&) in a < 0 because its use requires only a promotion.
Because compiler can not choose between bool operator <(const Foo &,const Foo &) and operator<(bool, int) which both fits in this situation.
In order to fix the issue make second constructor explicit:
struct Foo
{
Foo() {}
explicit Foo(int x) {}
operator bool() const { return false; }
friend bool operator<(const Foo& a, const Foo& b)
{
return true;
}
};
Edit:
Ok, at last I got a real point of the question :) OP asks why his compiler offers operator<(int, int) as a candidate, though "multi-step conversions are not allowed".
Answer:
Yes, in order to call operator<(int, int) object a needs to be converted Foo -> bool -> int. But, C++ Standard does not actually say that "multi-step conversions are illegal".
ยง 12.3.4 [class.conv]
At most one user-defined conversion (constructor or conversion
function) is implicitly applied to a single value.
bool to int is not user-defined conversion, hence it is legal and compiler has the full right to chose operator<(int, int) as a candidate.
This is exactly what the compiler tells you.
One approach for solving the if (a < 0) for the compiler is to use the Foo(int x) constructor you've provided to create object from 0.
The second one is to use the operator bool conversion and compare it against the int (promotion). You can read more about it in Numeric promotions section.
Hence, it is ambiguous for the compiler and it cannot decide which way you want it to go.