C++ New Style Casts in C (sort of)
Tags: c
Published: 2022-10-16 02:35:48

---
title: C++ New Style Casts in C (sort of)
published: true
description: Faking C++'s new style casts in C.
tags: #c
---

## Introduction

Occasionally, C++ features, such as function prototypes and `//` comments, get “back-ported” to C. Even though C++ has had “new style” casts ([`const_cast`](https://en.cppreference.com/w/cpp/language/const_cast), [`static_cast`](https://en.cppreference.com/w/cpp/language/static_cast), [`dynamic_cast`](https://en.cppreference.com/w/cpp/language/dynamic_cast), and [`reinterpret_cast`](https://en.cppreference.com/w/cpp/language/reinterpret_cast)) for [30 years](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/1993/N0349a.pdf), these have yet to be back-ported (if ever).  While the motivation for new style casts is detailed in Bjarne’s paper, it boils down to:

1. Make the intent of the program author clear to the reader.
2. Enable (or suppress) more compiler warnings.

Is there _any_ way to simulate new style casts in C?  Sort of.

## A First Cut

At the very least, we can define the following:

```c
#define CONST_CAST(T,EXPR)        ((T)(EXPR))
#define STATIC_CAST(T,EXPR)       ((T)(EXPR))
#define REINTERPRET_CAST(T,EXPR)  ((T)(EXPR))
```

(Noticeably absent is `DYNAMIC_CAST`.  C doesn’t support either `struct` inheritance or `virtual` functions, so a C style cast of `(T)expr` can never mean “dynamic cast” in C, hence its omission.)

Having such macros makes the intent of the program author clear to the reader.  For example, given:

```c
*p++ = (char*)opt;
```

was the intent to change the type of `opt`?  Cast away `const`?  Both?  We can’t tell.  However, using a macro:

```c
*p++ = CONST_CAST(char*, opt);
```

makes the intent clear.  The same would be true when using the other cast macros.  Clearer though the macros are, they’re still just [syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar).  Can they be made to be more than that?  Some of them can — a little.

## Improving the Macros

It turns out, unfortunately, that `CONST_CAST` and `STATIC_CAST` _can’t_ be improved.  However, since the former is used only for casting away `const` and the latter is used only for “well-behaved” casts (like casting an `int` to a `double`), it’s not that bad.  However, `REINTERPRET_CAST` _can_ be improved — a little.

Suppose there’s an API you’re using that allows “user data” to be given.  In C, this is usually done by passing your data to a `void*` parameter, for example when visiting the nodes of a [red-black tree](https://github.com/paul-j-lucas/cdecl/blob/master/src/red_black.h).  Assume we have a tree where the data we store at each node is:
```c
struct my_struct {
    // ...
    int value;
};
```
To find the first node having a given value:
```c
rb_node_t* find_value( rb_tree_t const *tree, int value ) {
    return rb_tree_visit(
        tree, &find_val_visitor,
        REINTERPRET_CAST(void*, value)
    );
}
```
where we cast `value` to `void*` to pass it for use as the data to be passed to the visitor function for each node visited:
```c
bool find_val_visitor( void *node_data, void *v_data ) {
    struct my_struct const *const ms = node_data;
    int const to_find = REINTERPRET_CAST(int, v_data);
    return ms->value == to_find;
}
```
In the visitor, we have to cast `node_data*` to `my_struct*` and `v_data` (the visitor data) back to an `int`.  The problem with the second cast is that `gcc` will give a warning (typically on 64-bit platforms):
```txt
prog.c: 42:52: warning: cast to smaller integer type 'int'
from 'void *' [-Wpointer-to-int-cast]
```
Normally, such a warning is a help; however, in this case, we _really_ mean to do the cast.  Is there any way to suppress the warning for _only_ this case?  If we redefined `REINTERPRET_CAST` like this:
```c
#define REINTERPRET_CAST(T,EXPR)  ((T)(uintmax_t)(EXPR))
```
then the warning goes away.  Why?  Because casting `void*` first to `uintmax_t` casts it to an integer type at least as large as `sizeof(void*)`; then casting _that_ to `int` generates no warning.

However, on a 32-bit platform where `sizeof(void*)` is 4 (yes, such platforms still exist), then we’d get the opposite warning when casting the `int` to `void*`:
```txt
prog.c 42:52: warning cast to pointer from integer of
different size [-Wint-to-pointer-cast]
```
The unfortunate conclusion is that there’s no way to define a single `REINTERPRET_CAST` macro that works for both integer/pointer casting _and_ casting between two non-pointer types.  Instead, we can define two macros:
```c
#define INTEGER_CAST(T,EXPR)      ((T)(uintmax_t)(EXPR))
#define POINTER_CAST(T,EXPR)      ((T)(uintptr_t)(EXPR))
```
where `INTEGER_CAST` is used to cast to or from an integer type from or to an opaque type (more later) and `POINTER_CAST` is used to cast to or from a pointer type.  Rewriting the code to use `POINTER_CAST`:
```c
bool find_val_visitor( void *node_data, void *v_data ) {
    struct my_struct const *const ms = node_data;
    int const to_find = POINTER_CAST(int, v_data);
    return ms->value == to_find;
}

rb_node_t* find_value( rb_tree_t const *tree, int value ) {
    return rb_tree_visit(
        tree, &find_val_visitor,
        POINTER_CAST(void*, value)
    );
}
```
makes the intent clear.

## `INTEGER_CAST`

What, exactly, is the use-case for `INTEGER_CAST`?  Suppose you have an API that defines an “opaque” type.  Opaque types are used where the implementation hides the exact type because it may vary by platform or may change in the future:
```c
typedef /* implementation defined */ api_user_data_t;
```
Despite that, you’re still supposed to write “user data” into it and read it back out again.

The red-black tree API presented earlier might want to allow you to pass the largest possible data around for visitor data.  Using `void*` on a 32-bit platform halves the size of such data from the more common 64-bit platforms.  To guarantee 64 bits regardless of platform, it might define the following instead of using `void*`:
```c
typedef uintmax_t visit_data_t;
```
However, we (as the API users) aren’t supposed to know (or care) about the underlying type.  We’d then rewrite our code using the opaque type:
```c
bool find_val_visitor( void *node_data, visit_data_t v_data ) {
    struct my_struct const *const ms = node_data;
    int const to_find = INTEGER_CAST(int, v_data);
    return ms->value == to_find;
}

rb_node_t* find_value( rb_tree_t const *tree, int value ) {
    return rb_tree_visit(
        tree, &find_val_visitor,
        INTEGER_CAST(visit_data_t, value)
    );
}
```
Since we’re now casting to or from an integer type from or to an opaque type — where we don’t know whether it’s a pointer or not — we _must_ use `INTEGER_CAST` to guarantee it’ll work with the largest possible integer.

## Conclusion

Use the right cast macro in the right circumstance:

+ `CONST_CAST` only for casting away `const`.
+ `STATIC_CAST` for “well-behaved” casts.
+ `POINTER_CAST` for casting to or from a pointer type.
+ `INTEGER_CAST` for casting to or from an integer from or to an opaque type.

Using these cast macros makes the intent of the program author clear to the reader. Use of `POINTER_CAST` (or `INTEGER_CAST`), unlike `STATIC_CAST`, additionally alerts the reader that a not-so-well-behaved cast is being performed and so should merit more scrutiny.