The title of this question is a little strange-sounding, but I could think of no better way to word it. My problem is this; I have a type inside a project called AmbiguousType
, which is a union in this format.
Declarations.h
(utility header file)
typedef union AmbiguousType
{
u32 unsigned32;
u64 unsigned64;
i32 signed32;
i64 signed64;
} AmbiguousType;
Now, I have many helper functions to better facilitate the use of this type. I was running through these functions, trying to puzzle out any ways to make them more efficient / less bulky, and realized something; most of the functions have a switch
statement as their main body. Take, for example, this function, which is used to assign to a given ambiguous type.
Declarations.c
(utility source file)
/**
* @brief Assign a value to an ambiguous type.
* @param affected The affected variable.
* @param member What state are we making the ambiguous type?
* @param value The value that state will have.
*/
void AssignAmbiguousType(AmbiguousType* affected, AmbiguousTypeSpecifier member,
void* value)
{
switch (member)
{
// Note that "VPTT" is a helper macro to turn a void pointer into the given type.
// U__ - unsigned __ bit integer.
// I__ - signed __ bit integer.
case unsigned32: affected->unsigned32 = VPTT(u32, value); return;
case unsigned64: affected->unsigned64 = VPTT(u64, value); return;
case signed32: affected->signed32 = VPTT(i32, value); return;
case signed64: affected->signed64 = VPTT(i64, value); return;
}
}
My question is, is there a pragmatic way to remove this switch
statement from the function, while keeping its use intact? It seems redundant to me, but I cannot figure out what could solve this issue.
I have tried using macros to simply combine tokens (##
), which obviously didn't work since the parameters are only known at runtime. I am at a loss here, though. Is there even a solution to this problem, or is this the best it'll get?
You can reduce repetitiveness using macros akin to X-macros. Here's an example.
// define once, include everywhere
#define GEN_SWITCH(DISCRIMINANT, BODY) \
switch(DISCRIMINANT) { \
case DISC(u,32) : BODY(u,32) break; \
case DISC(i,32) : BODY(i,32) break; \
case DISC(u,64) : BODY(u,64) break; \
case DISC(i,64) : BODY(i,64) break; \
default: cannot_happen(); }
#define CONCAT(a,b) a ## b
#define CONCAT2(a,b) CONCAT(a,b)
#define TYPENAME_i(sz) CONCAT2(signed,sz)
#define TYPENAME_u(sz) CONCAT2(unsigned,sz)
#define VPNAME(ui,sz) CONCAT(ui,sz)
#define FIELD(ui,sz) CONCAT2(TYPENAME_,ui)(sz)
#define DISC(ui,sz) CONCAT2(TYPENAME_,ui)(sz)
// more macros that map signedness+size to something
// When you need to generate a switch
#define BODY(ui,sz) affected->FIELD(ui,sz) = VPTT(VPNAME(ui,sz), value);
GEN_SWITCH(member, BODY)
The last line expands to the following (formatted for readability):
switch(member) {
case unsigned32 : affected->unsigned32 = VPTT(u32, value); break;
case signed32 : affected->signed32 = VPTT(i32, value); break;
case unsigned64 : affected->unsigned64 = VPTT(u64, value); break;
case signed64 : affected->signed64 = VPTT(i64, value); break;
default: cannot_happen();
}
This way every helper function will look like
#define BODY ... something ...
GEN_SWITCH(discriminant, BODY)