3

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.

Community
  • 1
  • 1
Slazer
  • 4,750
  • 7
  • 33
  • 60
  • Do you need the `int` constructor? Doesn't `BigUnsigned(unsigned long long value);` cover everything you want? – NathanOliver Jan 22 '16 at 21:06
  • @NathanOliver It seems it does. However, the assignment forces me to implement both of these constructors, probably to tackle exactly this kind of problem :). The `int` argument should be considered exactly as the `unsigned long long` on the bit level. – Slazer Jan 22 '16 at 21:07
  • explicit is good, implicit is bad. add a tie-breaker first argument, if possible with some self-descriptive name. if it's impossible to devise a reasonable name then the design is ungood and must be thrown on the nearest fire. – Cheers and hth. - Alf Jan 22 '16 at 21:08
  • @Cheersandhth.-Alf I am not allowed to alter the public interface. – Slazer Jan 22 '16 at 21:10
  • The question seems to be: what size do you want the long long to be? In the old days, you HAD to use long long to get 64-bits, but usually size_t is the proper size (and it's unsigned). This feels more like a "long long" number of bits is simply undefined. Can you use size_t (which is usually the biggest unsigned int the platform can support) or uint64_t (which is always 64 bits)? – rts1 Jan 22 '16 at 21:14
  • well, if you can't change anything, then it just like you can't change anything. problem solved: there's nothing to do, because you can't change anything. – Cheers and hth. - Alf Jan 22 '16 at 21:19
  • @Cheersandhth.-Alf Yes, I just realized that. This renders any answer useless for my assignment, but I am still curious. How would you solve it? You can change anything now. – Slazer Jan 22 '16 at 21:21
  • The question is : How do I make the C++ thing to call the `unsigned long long` constructor for integer literals higher than `max_int` and `int` constructor for lower integers? – Slazer Jan 22 '16 at 21:24
  • Oh. You should put that in the question. I would think brace init syntax would do the trick. Checking. – Cheers and hth. - Alf Jan 22 '16 at 21:26
  • No, that didn't work. So, you have to do some redesigning. I'd opt for the explicit constructor choice via tie-breaking argument, an old idiom called "named constructors" (it's in the FAQ). Or you can make a public constructor template that delegates to others, if it's important to know the type of the actual argument. It depends much on what this is *for*. – Cheers and hth. - Alf Jan 22 '16 at 21:34
  • @Cheersandhth.-Alf You mean the purpose of my class? It takes a variable of any unsigned integer type and makes it an arbitrary-length unsigned integer object. Much like its name - `BigUnsigned`. I kind of like more the template delegation. If you could point me to a solution I'd accept it. – Slazer Jan 22 '16 at 21:46
  • For that purpose you can just remove the `int` argument constructor. – Cheers and hth. - Alf Jan 22 '16 at 22:08

2 Answers2

3

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).

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • `unsigned short` to `unsigned int` is only a promotion if `int` can't represent all the values of `unsigned short`. – T.C. Jan 23 '16 at 10:41
0

I had the same Problem. Also with some Big-Integer classes. However the literal-only solution did not work for me since I need to convert totally different kinds of (Big-)Ints between each other. I tried to avoid the excessive implementation of constructors. I solved the problem like this:

  template <std::same_as<MyOtherBigInt> T>
  explicit constexpr MyBigInt(T pValue) noexcept;

This stops the special MyOtherBigInt-Constructor from being called with any other integer.

Silicomancer
  • 8,604
  • 10
  • 63
  • 130