error: use of overloaded operator '==' is ambiguous

Sorry for the longish code example attached to this email but I am
trying to understand if clang is being overly pedantic in this
situation. Also please let me know if I should just file defects
instead of posting to this mailing list (unsure if this is a clang
problem or not).

It seems like clang should be ignoring the private bool operators when
searching for a candidate. I assume the built-in ones would be
ignored/not listed if the only direct candidate was int32_t.

gcc likes the code...

[serickson@serickson-main:~/Development/projects/llvm_cases]
[0:656] > c++-4.2 -c issue_002.cpp

clang doesn't...

[serickson@serickson-main:~/Development/projects/llvm_cases]
[0:658] > flc --version
clang version 2.8 (trunk 109812)
Target: x86_64-apple-darwin10
Thread model: posix

[serickson@serickson-main:~/Development/projects/llvm_cases]
[0:657] > flc -c issue_002.cpp
issue_002.cpp:78:21: error: use of overloaded operator '==' is ambiguous
    if (Status::eNo == bar.isEmpty()) {
        ~~~~~~~~~~~ ^ ~~~~~~~~~~~~~
issue_002.cpp:78:21: note: because of ambiguity in conversion of
'ResultCode' (aka 'ResultCodeClass') to 'long double'
issue_002.cpp:10:5: note: candidate function
    operator const int32_t&() throw() { return _code; } //lint !e1762
// don't make this const
    ^
issue_002.cpp:11:5: note: candidate function
    operator const int32_t&() const throw() { return _code; }
    ^
issue_002.cpp:46:5: note: candidate function
    operator bool();
    ^
issue_002.cpp:47:5: note: candidate function
    operator const bool();
    ^
issue_002.cpp:78:21: note: built-in candidate operator==(long long, long double)
    if (Status::eNo == bar.isEmpty()) {
                    ^
issue_002.cpp:78:21: note: built-in candidate operator==(long long, double)
issue_002.cpp:78:21: note: built-in candidate operator==(long long, float)
issue_002.cpp:78:21: note: built-in candidate operator==(long long,
unsigned long long)
issue_002.cpp:78:21: note: built-in candidate operator==(long long,
unsigned long)
issue_002.cpp:78:21: note: built-in candidate operator==(long long,
unsigned int)
issue_002.cpp:78:21: note: built-in candidate operator==(long long, long long)
issue_002.cpp:78:21: note: built-in candidate operator==(long long, long)
issue_002.cpp:78:21: note: built-in candidate operator==(long long, int)
issue_002.cpp:78:21: note: built-in candidate operator==(float, long double)
issue_002.cpp:78:21: note: built-in candidate operator==(float, double)
issue_002.cpp:78:21: note: built-in candidate operator==(float, float)
issue_002.cpp:78:21: note: built-in candidate operator==(float,
unsigned long long)
issue_002.cpp:78:21: note: built-in candidate operator==(float, unsigned long)
issue_002.cpp:78:21: note: built-in candidate operator==(float, unsigned int)
issue_002.cpp:78:21: note: built-in candidate operator==(float, long long)
issue_002.cpp:78:21: note: built-in candidate operator==(float, long)
issue_002.cpp:78:21: note: built-in candidate operator==(float, int)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned long
long, long double)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned long
long, double)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned long
long, float)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned long
long, unsigned long long)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned long
long, unsigned long)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned long
long, unsigned int)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned long
long, long long)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned long
long, long)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned long
long, int)
issue_002.cpp:78:21: note: built-in candidate operator==(long double,
long double)
issue_002.cpp:78:21: note: built-in candidate operator==(long double, double)
issue_002.cpp:78:21: note: built-in candidate operator==(long double, float)
issue_002.cpp:78:21: note: built-in candidate operator==(long double,
unsigned long long)
issue_002.cpp:78:21: note: built-in candidate operator==(long double,
unsigned long)
issue_002.cpp:78:21: note: built-in candidate operator==(long double,
unsigned int)
issue_002.cpp:78:21: note: built-in candidate operator==(long double, long long)
issue_002.cpp:78:21: note: built-in candidate operator==(long double, long)
issue_002.cpp:78:21: note: built-in candidate operator==(long double, int)
issue_002.cpp:78:21: note: built-in candidate operator==(double, long double)
issue_002.cpp:78:21: note: built-in candidate operator==(double, double)
issue_002.cpp:78:21: note: built-in candidate operator==(double, float)
issue_002.cpp:78:21: note: built-in candidate operator==(double,
unsigned long long)
issue_002.cpp:78:21: note: built-in candidate operator==(double, unsigned long)
issue_002.cpp:78:21: note: built-in candidate operator==(double, unsigned int)
issue_002.cpp:78:21: note: built-in candidate operator==(double, long long)
issue_002.cpp:78:21: note: built-in candidate operator==(double, long)
issue_002.cpp:78:21: note: built-in candidate operator==(double, int)
issue_002.cpp:78:21: note: built-in candidate operator==(long, long double)
issue_002.cpp:78:21: note: built-in candidate operator==(long, double)
issue_002.cpp:78:21: note: built-in candidate operator==(long, float)
issue_002.cpp:78:21: note: built-in candidate operator==(long,
unsigned long long)
issue_002.cpp:78:21: note: built-in candidate operator==(long, unsigned long)
issue_002.cpp:78:21: note: built-in candidate operator==(long, unsigned int)
issue_002.cpp:78:21: note: built-in candidate operator==(long, long long)
issue_002.cpp:78:21: note: built-in candidate operator==(long, long)
issue_002.cpp:78:21: note: built-in candidate operator==(long, int)
issue_002.cpp:78:21: note: built-in candidate operator==(int, long double)
issue_002.cpp:78:21: note: built-in candidate operator==(int, double)
issue_002.cpp:78:21: note: built-in candidate operator==(int, float)
issue_002.cpp:78:21: note: built-in candidate operator==(int, unsigned
long long)
issue_002.cpp:78:21: note: built-in candidate operator==(int, unsigned long)
issue_002.cpp:78:21: note: built-in candidate operator==(int, unsigned int)
issue_002.cpp:78:21: note: built-in candidate operator==(int, long long)
issue_002.cpp:78:21: note: built-in candidate operator==(int, long)
issue_002.cpp:78:21: note: built-in candidate operator==(int, int)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned
long, long double)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned long, double)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned long, float)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned
long, unsigned long long)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned
long, unsigned long)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned
long, unsigned int)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned
long, long long)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned long, long)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned long, int)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned int,
long double)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned int, double)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned int, float)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned int,
unsigned long long)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned int,
unsigned long)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned int,
unsigned int)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned int,
long long)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned int, long)
issue_002.cpp:78:21: note: built-in candidate operator==(unsigned int, int)
1 error generated.

issue_002.cpp (3.11 KB)

Usually it's okay to just file... but since we're discussing it here,
might as well continue.

I believe the issue reduces to the following:

struct A
{
    operator int();
    operator bool();
};

int main(int argc, char** argv) {
    if (1000 == A()) {
    }
}

I don't know the rules here off the top of my head...

-Eli

Sorry for the longish code example attached to this email but I am
trying to understand if clang is being overly pedantic in this
situation. Also please let me know if I should just file defects
instead of posting to this mailing list (unsure if this is a clang
problem or not).

Filing bugs is generally nicer.

It seems like clang should be ignoring the private bool operators when
searching for a candidate.

Access doesn't affect visibility in C++: first you decide which operator you want,
then you decide whether you can access it.

I assume the built-in ones would be
ignored/not listed if the only direct candidate was int32_t.

If by 'direct candidate' you mean a member function candidate, you don't
consider member operators unless the left operand is of class type. In this
case, Status::eNo has type ResultCodeConst, i.e. int64_t. Neither ADL not
unqualified lookup find any user-defined operators, so the only possibilities
are the builtin candidates.

That said, I think the builtin candidate at (int64_t, int32_t) should be viable;
Doug, thoughts?

John.

Clang is doing the right thing here (and EDG agrees with Clang that this code is ill-formed due to an ambiguity), but it takes a bit of standards-reading to see why. Let's go with Eli's reduction of the issue, here, although the issue is the same:

struct A
{
   operator int();
   operator bool();
};

int main(int argc, char** argv) {
   if (1000 == A()) {
   }
}

And we'll consider two built-in candidates (of the many required by C++ 13.6 [over.built]p12):

  operator==(int, int)
  operator==(int, float)

For both candidates, the first argument is trivially an exact match. It's the second argument, of type A, that is interesting.

For operator==(int, int), we perform overload resolution to find a conversion from A to int. Per C++ 13.3.1.5 [over.match.conv]p1, we consider all of the conversion functions in A as candidates. Both conversion functions work, so we perform overload resolution to see which conversion function is better. C++ 13.3.3 [over.match.best]p1 tells us that, since we're performing initialization by user-defined conversion, we can compare the last conversion sequences for the two conversion functions:

  for operator int(), we have an exact match
  for operator bool(), we have an integral promotion

An exact match is better than an integral promotion, so we pick the operator int() conversion sequence. Therefore, the conversion sequence for the second argument to operator==(int, int) is identity->user-defined operator int()->identity.

For operator==(int, float), we again perform overload resolution using the two conversion functions in A, but this time we're converting to float. The last conversion sequences for the two conversion functions are:

  for operator int(), we have an integral-to-floating conversion
  for operator bool(), we have an integral-to-floating conversion

The two integral-to-floating conversion functions are indistinguishable, so the conversion sequence for the second argument to operator==(int, float) is the ambiguous conversion sequence (C++ 13.3.3.1 [over.best.ics]p10).

Now, we go to see which of operator==(int, int) or operator==(int, float) is better. The conversion sequences for the first argument are simple: both are exact matches, neither is better than the other. For the second argument, we're comparing a user-defined conversion sequence (using A::operator int()) against the ambiguous conversion sequence. Per C++ 13.3.3.1 [over.best.ics]p10, "the ambiguous conversion sequence is treated as a user-defined sequence that is indistinguishable from any other user-defined conversion sequence", meaning that we can't order these two. Hence, there is an ambiguity and the code is ill-formed.

I don't know why GCC doesn't catch this ambiguity. My hunch is that it isn't including all of the built-in operator candidates required by the standard, since I've seen such problems with GCC before.

To fix the original code, you can add overloaded operator=='s such as

  bool operator==(int32_t, ResultCodeClass);
  bool operator==(ResultCodeClass, int32_t);

  - Doug

Thanks Doug for that analysis.

Quick question why is long double being listed in the following
diagnostic? I don't believe either type at that line are floating
point (the const Status::eNo is int64_t) unless this is some fallback
promotion.

[0:657] > flc -c issue_002.cpp
issue_002.cpp:78:21: error: use of overloaded operator '==' is ambiguous
   if (Status::eNo == bar.isEmpty()) {
       ~~~~~~~~~~~ ^ ~~~~~~~~~~~~~
issue_002.cpp:78:21: note: because of ambiguity in conversion of
'ResultCode' (aka 'ResultCodeClass') to 'long double'

It doesn't matter that 'long double' itself isn't used. All of the builtin candidates are required to be in the overload set, per C++ 13.6 [over.built]p12:

For every pair of promoted arithmetic types L and R, there exist candidate operator functions of the form
  LR operator*(L, R);
  LR operator/(L, R);
  LR operator+(L, R);
  LR operator-(L, R);
  bool operator<(L, R);
  bool operator>(L, R);
  bool operator<=(L, R);
  bool operator>=(L, R);
  bool operator==(L, R);
  bool operator!=(L, R);

where LR is the result of the usual arithmetic conversions between types L and R.

It's possible that GCC was attempting to optimize by not providing the candidates involving floating-point types, but that optimization would be incorrect.

  - Doug