2

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?

7
  • Assuming your ISA is little-endian, and if you don't mind some nasal demons, then you can just ditch the union type entirely: just pass around a single u64 value because it can be the source of reads of smaller typed values and the target of writes to smaller integer types.
    – Dai
    Commented Jun 16 at 1:16
  • @Zenais, Make AmbiguousType a member of AmbiguousStruct that has a 2nd member which is a pointer to a set of functions to use with this instance of AmbiguousStruct. Commented Jun 16 at 1:18
  • @chux-ReinstateMonica A vtable in C? ...that's heresey in some parts of the world (more seriously though; how about appropriating the MSB of the u64 as a type-tag, provided the OP is okay with reducing it to a 56-bit integer?)
    – Dai
    Commented Jun 16 at 1:19
  • @Dai, vtable true, yet sounds like fun to spin a C++ light. (or is C++ light an oxymoron?) Commented Jun 16 at 1:20
  • 2
    @Dai, Note OP only needs 2 bits, so uint62_t, yet this approach (2 bits or a byte) still does not rid the switch(). Commented Jun 16 at 1:22

1 Answer 1

1

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)
1
  • 1
    I apologize for the late reply, I was on vacation. Anyway, thank you so much for this! I don't know how I did not realize to do this sooner, this satisfies all of my criteria (i.e. prettier) and yet doesn't have any performance overhead. Thank you.
    – Zenais
    Commented Jun 21 at 18:49

Not the answer you're looking for? Browse other questions tagged or ask your own question.