Date: Thu, 17 Sep 2026 09:00:14 +0100
Oh nevermind, the definition was abbreviated and specifically says that my case doesn't happen. Full definition reproduced below for other people's convenience:
> If T is a (possibly cv-qualified) unsigned integer type (6.8.2) then the
member typedef type denotes T; otherwise, if T is a (possibly
cv-qualified) signed integer type then type denotes the corresponding
unsigned integer type, with the same cv-qualifiers as T; otherwise,
type denotes the unsigned integer type with smallest rank (6.8.6) for
which sizeof(T) == sizeof(type), with the same cv-qualifiers as
T.
T is required to be an integral type other than bool so the "otherwise" clause only effects character types. So yes adapting my example even if the underlying type of char32_t is uint32_t then make_unsigned_t<char32_t> would be required to provide uint24_t
On 17 September 2026 08:42:12 BST, Jennifier Burnett <jenni_at_[hidden]> wrote:
>If an implementation provides an extended integer type with the same size as a standard integer type the rank rules say that the standard type should have a higher rank than the extended type, thus if an implementation provides uint24_t with sizeof(uint24_t) == sizeof(uint32_t) then that gets you that make_unsigned_t<uint32_t> must provide uint24_t, which seems odd.
>
>On 17 September 2026 07:56:36 BST, Keenan Horrigan via Std-Discussion <std-discussion_at_[hidden]> wrote:
>>Hello,
>>
>>Recently I've been working on some code that needs to deal somewhat finely with integer/integral types, so I've been going and reading the standard to make sure that I'm staying within the bounds of the standard with regards to integer types. Something that caught my eye, and is somewhat meaningful to my code, is the specification of 'std::make_unsigned', which can be seen here: https://eel.is/c++draft/meta.trans.sign
>>
>>For signed and unsigned integer types, it works how I would expect, but for other types it's specified as yielding "the unsigned integer type with smallest rank ([conv.rank]) for which sizeof(T) == sizeof(type)". And what's confusing to me about that is that it talks about the *size* of the type, rather than the *width* of the type, which I think could hypothetically lead to getting a type which may have the same size as the original type, but which has a smaller width and therefore a smaller value range.
>>
>>For instance, we might try to calculate 'make_unsigned_t<char16_t>', and let's assume that we're on a platform where char16_t has a size of two bytes and a width of 16. To my eye, that call to 'make_unsigned' is not required to yield what one might expect, that being the actual underlying type of char16_t, 'std::uint_least16_t', nor even a type that has the same properties. If we were to imagine that our implementation provides an extended unsigned integer type that has a size of two bytes, but which has 8 padding bits and a width of 8, then, if I'm understanding all these rules correctly, that extended integer type would have to have a lower rank than 'std::uint_least16_t', since it has a smaller width. And so according to the rules for 'make_unsigned', the implementation would have to yield that extended type with the smaller width and the smaller value range before it would yield 'std::uint_least16_t', or some other comparable integer type.
>>
>>So what I'm seeking clarification on is, does it actually work this way or have I misunderstood something? And if it does work that way, is it supposed to? I'd be able to work around this in my code if need be, so it's not so big a deal to me, but it has piqued my interest.
>>
>>Thanks
> If T is a (possibly cv-qualified) unsigned integer type (6.8.2) then the
member typedef type denotes T; otherwise, if T is a (possibly
cv-qualified) signed integer type then type denotes the corresponding
unsigned integer type, with the same cv-qualifiers as T; otherwise,
type denotes the unsigned integer type with smallest rank (6.8.6) for
which sizeof(T) == sizeof(type), with the same cv-qualifiers as
T.
T is required to be an integral type other than bool so the "otherwise" clause only effects character types. So yes adapting my example even if the underlying type of char32_t is uint32_t then make_unsigned_t<char32_t> would be required to provide uint24_t
On 17 September 2026 08:42:12 BST, Jennifier Burnett <jenni_at_[hidden]> wrote:
>If an implementation provides an extended integer type with the same size as a standard integer type the rank rules say that the standard type should have a higher rank than the extended type, thus if an implementation provides uint24_t with sizeof(uint24_t) == sizeof(uint32_t) then that gets you that make_unsigned_t<uint32_t> must provide uint24_t, which seems odd.
>
>On 17 September 2026 07:56:36 BST, Keenan Horrigan via Std-Discussion <std-discussion_at_[hidden]> wrote:
>>Hello,
>>
>>Recently I've been working on some code that needs to deal somewhat finely with integer/integral types, so I've been going and reading the standard to make sure that I'm staying within the bounds of the standard with regards to integer types. Something that caught my eye, and is somewhat meaningful to my code, is the specification of 'std::make_unsigned', which can be seen here: https://eel.is/c++draft/meta.trans.sign
>>
>>For signed and unsigned integer types, it works how I would expect, but for other types it's specified as yielding "the unsigned integer type with smallest rank ([conv.rank]) for which sizeof(T) == sizeof(type)". And what's confusing to me about that is that it talks about the *size* of the type, rather than the *width* of the type, which I think could hypothetically lead to getting a type which may have the same size as the original type, but which has a smaller width and therefore a smaller value range.
>>
>>For instance, we might try to calculate 'make_unsigned_t<char16_t>', and let's assume that we're on a platform where char16_t has a size of two bytes and a width of 16. To my eye, that call to 'make_unsigned' is not required to yield what one might expect, that being the actual underlying type of char16_t, 'std::uint_least16_t', nor even a type that has the same properties. If we were to imagine that our implementation provides an extended unsigned integer type that has a size of two bytes, but which has 8 padding bits and a width of 8, then, if I'm understanding all these rules correctly, that extended integer type would have to have a lower rank than 'std::uint_least16_t', since it has a smaller width. And so according to the rules for 'make_unsigned', the implementation would have to yield that extended type with the smaller width and the smaller value range before it would yield 'std::uint_least16_t', or some other comparable integer type.
>>
>>So what I'm seeking clarification on is, does it actually work this way or have I misunderstood something? And if it does work that way, is it supposed to? I'd be able to work around this in my code if need be, so it's not so big a deal to me, but it has piqued my interest.
>>
>>Thanks
Received on 2026-09-17 08:00:28
