Search code examples
c++operator-overloadingoverload-resolution

Why is operator& ambiguous when only one overload is valid?


The code:

#include <stdint.h>

struct HashType64
{
    inline HashType64(uint64_t h) noexcept : _h(h) {}
    inline operator uint64_t() const noexcept { return _h; }
    inline operator int64_t() const = delete;
    inline operator int32_t() const = delete;
    inline operator uint32_t() const = delete;

private:
    uint64_t _h;
};

uint64_t f() {
    return HashType64(0) & 0xFFULL;
}

The error:

<source>: In function 'uint64_t f()':
<source>:17:26: error: ambiguous overload for 'operator&' (operand types are 'HashType64' and 'long long unsigned int')
   17 |     return HashType64(0) & 0xFFULL;
      |            ~~~~~~~~~~~~~ ^ ~~~~~~~
      |            |               |
      |            HashType64      long long unsigned int
<source>:17:26: note: candidate: 'operator&(uint32_t {aka unsigned int}, long long unsigned int)' (built-in)
   17 |     return HashType64(0) & 0xFFULL;
      |            ~~~~~~~~~~~~~~^~~~~~~~~
<source>:17:26: note: candidate: 'operator&(int32_t {aka int}, long long unsigned int)' (built-in)
<source>:17:26: note: candidate: 'operator&(int64_t {aka long int}, long long unsigned int)' (built-in)
<source>:17:26: note: candidate: 'operator&(uint64_t {aka long unsigned int}, long long unsigned int)' (built-in)

I understand what the error is about, but I do not understand why it occurs. HashType64 is only convertible to uint64_t and not to any other integer types, the other overloads are not applicable, how can it be ambiguous?

https://godbolt.org/z/5zf1MPY4Y


Solution

  • When doing overload resolution for & there are candidates for the built-in & operator.

    More specifically there is one overload with the signature operator&(L, R) for each promoted integer type L and and each promoted integer type R (not necessarily the same).

    Therefore, even with no conversion on the right-hand side there are at least candidates

    operator&(int,                unsigned long long)
    operator&(unsigned,           unsigned long long)
    operator&(long,               unsigned long long)
    operator&(unsigned long,      unsigned long long)
    operator&(long long,          unsigned long long)
    operator&(unsigned long long, unsigned long long)
    

    HashType64 has, on your system according to the error message, conversion operators to int (int32_t), unsigned int (uint32_t), long (int64_t), unsigned long (uint64_t).

    That these overloads are defined as deleted doesn't affect overload resolution and so each of the four upper overloads are viable. None of them requires more than a single user-defined conversion without another conversion following it for the first argument and so they are all equally viable. (Even if there were standard conversions following the user-defined conversion, conversion sequences involving different user-defined conversions, e.g. conversion functions, are always considered ambiguous.) Nothing else differentiates them in overload resolution and so it is ambiguous.

    If you don't want an overload to participate in overload resolution, then you can't declare it at all. The purpose of = delete is to still have the function be considered in overload resolution as usual, but have overload resolution fail if the deleted overload would be chosen. It is useful to disallow certain arguments from being used that would otherwise choose a non-deleted overload with conversions/reference binding that is unintended.