(Self-answering because of lack of non-C++ answers)
In C99 the only way to have the overloaded-function-like behaviour like this is through preprocessor tricks. The union wouldn't work, at the very least because the semantics of such a union are undecided in GCC itself, have been for some years and don't seem to be changing. In that bug report and discussion looks like GCC developers aren't really sure about what to interpret from the standard… so I took that as a very strong suggestion to abandon this whole angle of attack, even if I changed compilers.
So finally I went with generalized preprocessor "templates" which declared and initialized functions, structures, etc with parameterized types - for example, a uint16
in one case and a volatile uint8
in another. For example, for the circular buffer:
#define CIRCBUF_TYPE_DECLARE(TYPE, TYPELABEL, QUALIF, QUALIFLABEL) \
struct circbuf##TYPELABEL##QUALIFLABEL##_st { \
const TYPE buffer_num_elems; \
TYPE QUALIF head; \
TYPE QUALIF tail; \
uint8_t QUALIF * buffer; \
}; \
\
typedef struct circbuf##TYPELABEL##QUALIFLABEL##_st circbuf##TYPELABEL##QUALIFLABEL;
And then invoked like so:
CIRCBUF_TYPE_DECLARE(uint8_t, _u8, volatile, _v)
For this kind of thing, Jens Gustedt's P99 preprocessor library for C was a great inspiration. Later I found that this kind of "preprocessor templating" is also used in Linux's kernel source code… So even though I rolled my own versions probably with time I would have tried using one of the existing implementations. However, I switched jobs before I had the time for that.
For those interested, I wrote a blog post with the full discussion on the C99-standard-based justification of why the volatile+non-volatile union is legal and yet how the semantics are still not clear.