Casting to the Same-Sized Unsigned Type
Tags: c
Published: 2024-07-10 23:08:33

---
title: Casting to the Same-Sized Unsigned Type
published: true
description: Given an integral expression, how to cast it to an unsigned type of the same size.
tags: #c
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2024-07-10 21:47 +0000
---

## Introduction

In [cdecl](https://github.com/paul-j-lucas/cdecl), there’s this [enumeration](https://dev.to/pauljlucas/enumerations-in-c-ae7):

```c
enum cdecl_show {
  CDECL_SHOW_PREDEFINED       = 1 << 0,
  CDECL_SHOW_USER_DEFINED     = 1 << 1,
  CDECL_SHOW_OPT_IGNORE_LANG  = 1 << 2
};
typedef enum cdecl_show cdecl_show_t;
```

whose values are bit-flags that can be bitwise-or’d together.

> What the flags do isn’t important here, but, briefly, they control which types are shown in response to a cdecl `show` command.

I was working on enhancing the behavior of the `show` command such that if no user-defined type having a specific name was shown, show a predefined type having the same name, if any, via code like:

```c
if ( !showed_any && (show & CDECL_SHOW_USER_DEFINED) != 0 ) {
  show &= ~CDECL_SHOW_USER_DEFINED;
  show |= CDECL_SHOW_PREDEFINED;
  // ...
}
```

That is, turn off the `CDECL_SHOW_USER_DEFINED` bit and turn on the `CDECL_SHOW_PREDEFINED` bit.  The problem was, when compiling with the `Wsign-conversion` compiler option, I got:

```
show.c:244:8: warning: implicit conversion changes signedness: 'int' to 'unsigned int' [-Wsign-conversion]
  show &= ~CDECL_SHOW_USER_DEFINED;
       ~~ ^~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
```

This happens because enumeration values in C implicitly convert to their underlying type (here, `int`), but the `~` converts its operand to `unsigned int`.

The obvious way to silence the warning is to cast to `unsigned` first:

```c
  show &= ~(unsigned)CDECL_SHOW_USER_DEFINED;  // no warning
```

The problem is, in C prior to [C23](https://en.wikipedia.org/wiki/C23_(C_standard_revision)), you can’t be sure what the underlying type of an enumeration is.  From the C11 standard §6.7.2.2 ¶4:

> Each enumerated type shall be compatible with `char`, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined.

If you don’t know what the underlying type is, in particular its size, you don’t know that `unsigned int` is the right choice of unsigned type because you want the sizes to match.

Given that `cdecl_show` has only the values 1, 2, and 4, it’s a pretty safe bet that its underlying type is `int` — but you can’t be _sure_.  What’s needed is a way to cast to an unsigned type that’s the same size as the type of a given expression.

## The Solution

Using [`_Generic`](https://dev.to/pauljlucas/generic-in-c-i48) and `STATIC_IF` (given in that article), we can implement:

```c
#define TO_UNSIGNED(N)                    \
  STATIC_IF( sizeof(N) == sizeof(char),   \
    (unsigned char)(N),                   \
  STATIC_IF( sizeof(N) == sizeof(short),  \
    (unsigned short)(N),                  \
  STATIC_IF( sizeof(N) == sizeof(int),    \
    (unsigned int)(N),                    \
  STATIC_IF( sizeof(N) == sizeof(long),   \
    (unsigned long)(N),                   \
    (unsigned long long)(N) ) ) ) )
```

where `N` is any numeric expression of any integral or enumeration type.  The implementation is straight-forward: use a chain of `STATIC_IF`s to determine the size of the type of the expression `N`, then cast it to an unsigned type of the same size.

Given that, I can now write:

```c
  show &= ~TO_UNSIGNED( CDECL_SHOW_USER_DEFINED );
```

and get no warning.

## Conclusion

Once again, `_Generic` allows you to do some compile-time type introspection.  The nice thing about `TO_UNSIGNED()` is that it will always work even if the underlying type changes.

In addition to being useful for enumerations, it’s also useful for `typedef`s of integral types in that you’ll never have to look up what the underlying type of a `typedef` is to know which unsigned type to cast to.