Search code examples
c++typesg++usingunsigned-integer

Strange result of using T1 = unsigned T2 in C++


The following code confuses me.

https://godbolt.org/z/WcMTYM1q7

#include <iostream>

using ll = long long;
using ull = unsigned ll;

int main() {
    ull x = 0;
    std::cout << x << std::endl;
}

This code fails to compile, in g++ 11, but successful to compile in g++ 12 and g++ 13. In g++ 11, compiler says:

error: ambiguous overload for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream<char>'} and 'ull' {aka 'long unsigned int'})

And I also test the following code.

https://godbolt.org/z/PrePfoa6E

#include <iostream>

using ll = long long;
using ull = unsigned ll;
using ull2 = std::make_unsigned_t<ll>;

int main() {
    std::cout << typeid(ll).name() << std::endl;
    std::cout << typeid(ull).name() << std::endl;
    std::cout << typeid(ull2).name() << std::endl;
}

When using g++ 13 to compile the code, it prints x, j and y, which mean long long, unsigned int and unsigned long long. And when using g++ 11 to compile the code, it prints x, m and y, which mean long long, unsigned long and unsigned long long. All in all, unsigned ll is not the expected type.

Why does this happen? Is it a compiler bug? When should I use std::make_unsigned?


Solution

  • using ull = unsigned ll; is not valid standard C++. You can't add a type-specifier like unsigned to a typedef name like ll. That's only possible for cv-qualifiers, i.e. const and volatile, not other qualifiers that change the type like unsigned.

    The compiler should issue a diagnostic for this code and GCC does correctly do so with -pedantic or -pedantic-errors.

    That it compiles at all without these flags, but doesn't with them, indicates that it is likely an intended non-standard-conforming behavior to accept this declaration and in that case there is no reason to assume that unsigned ll would be unsigned long long absent documentation saying so.

    The documentation mentions that this syntax was allowed in K&R C for typedefs, but not in ISO C. Presumably this is left over for K&R C compatibility where long long and unsigned long long don't exist and so this syntax seems to not be implemented as expected for these types. And the C++11 using declaration is basically just syntactical sugar for typedef, so the behavior was probably taken from the typedef behavior, even if it doesn't make sense for compatibility. -pedantic-errors enforces standards-conformance, so that this compatibility feature is not permitted anymore.

    However, the overload resolution failure for GCC 11 looks unintended to me either way and could be explained as a bug fixed in the later versions.