c++c++11integertype-conversioninteger-promotion

Ambiguous call to overloaded integer constructor


I shall provide these two constructors.

BigUnsigned(int value)
    :BigUnsigned(static_cast<unsigned long long>(value)){
}
BigUnsigned(unsigned long long value);

The problem is the call is ambiguous for some argument values. According to this answer, that refers the C++11 standard,

the type of an integer literal is the first of the corresponding list in Table 6 in which its value can be represented.

Table 6 is here

int
long int
long long int

Therefore, in my case, the type of the constructor argument (integer literal) if it belongs to the range...

<0, numeric_limits<int>::max()> is int

---> calling BigUnsigned(int)

(numeric_limits<int>::max(), numeric_limits<long int>::max()> is long int

---> ambiguous

(numeric_limits<long int>::max(), too big literal) is long long int

---> ambiguous

How can I solve the ambiguity without declaring any more constructors or explicitly typecasting the argument?

This question on integer promotion and integer conversion might be useful. I still don't know which of them applies in my case, though.


Solution

  • One fundamental problem here is that a decimal literal will never be deduced as an unsigned type. Therefore, a decimal literal that's too large to fit in an int end up requiring a signed->unsigned conversion in one case, and a long->int conversion in the other. Both of these are classed as "integral conversions", so neither is considered a "better" conversion, and the overload is ambiguous.

    As to possible ways to deal with this without explicitly casting the argument or adding more constructors, I can see a couple.

    At least for a literal, you could add a suffix that specifies that the type of the literal is unsigned:

    BigUnsigned a(5000000000U); // unambiguous
    

    Another (that also applies only to literals) would be to use a hexadecimal or octal literal, which (according to part of table 6 that isn't quoted in the question) can be deduced as either signed or unsigned. This is only a partial fix though--it only works for values that will deduce as unsigned. For a typical system with 32-bit int, 32-bit long, and 64-bit long long, I believe it'll come out like this:

    enter image description here

    So for a parameter large enough that it won't fit in a signed long long, this gives an unambiguous call where the decimal constant would still have been ambiguous.

    For people who've worked with smaller types, it might initially seem like the conversion from unsigned long to unsigned long long would qualify as a promotion instead of a conversion, which would make it preferable. And indeed, if (for example) the types involved were unsigned short and unsigned int, that would be exactly true--but that special preference is only given for types with conversion ranks less than int (which basically translates to: types that are smaller than int).

    So that fixes the problem for one range of numbers, but only if they're literals, and only if they fall into one specific (albeit, quite large) range.

    For the more general case, the only real cure is to change the interface. Either remove the overload for int, or add a few more ctor overloads, specifically for unsigned and for long long. These can be delegating constructors just like the existing one for int, if you decide you need them at all (but it's probably better to just have the one for unsigned long long and be done with it).