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 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:
float
to int
)(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.
The problem here is that C++ has two options to deal with a < 0
expression:
a
to bool
, and compare the result to 0
with built-in operator <
(one conversion)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)) {
...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With