cswitch-statementunions

Is There a Pragmatic Solution to Remove Enum Switches?


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?


Solution

  • 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)