2

I have a number of functions that need to be called when the program is executed in order to find out the capabilities of the device that the program uses. I thought that this would be a great example to use a union of function pointers. Here's a snippet of the code that should explain what I'm going for :

struct multi_fn
{
        union
        {
                void (*type1)(float);
                void (*type2)(unsigned char*);
        } func_ptr;
        enum
        { FUNC_TYPE1, FUNC_TYPE2 } func_type;
};

void type1func(float);
void type2func(unsigned char*);

static const struct multi_fn the_funcs[] = {
        { { .type1 = type1func }, FUNC_TYPE1 },
        { { .type2 = type2func }, FUNC_TYPE2 }
};

As you can see, initialization of the union in this example is achieved by the means of .type1 = type1func. However, this syntax is not valid C90 : although gcc accepts it, only issuing a warning when -pedantic is on, MSVC refuses to compile such code.

I'm having trouble in finding another way around it : obviously, the best way to do this would be to use a struct instead of an union, and that's probably what I'm going to do if there is no better way. The array is going to be filled with compile-time constants only, which is pretty much the whole motivation behind it.

Knowing that C90 only allows to initialize the first member of a union, it seems to me that they're not of much use in a case like this : even if I wanted to initialize only the first member of the union, and later access it via its other member (since the written-to type can be determined via the accompanying enum value), it would still be undefined behaviour.

I thought about storing the function pointer as a void* and then casting it to its proper type via the enum value, however casting function pointers to void* is also forbidden.

The question is : is there another way to do such a thing? C90 is, unfortunately, a must.

Daniel Kamil Kozar
  • 18,476
  • 5
  • 50
  • 64
  • "if I wanted to initialize only the first member of the union, and later access it via its other member, it would still be undefined behaviour." This is implementation-defined in C90: "if a member of a union object is accessed after a value has been stored in a different member of the object, the behavior is implementation-defined." – tab May 21 '14 at 04:43

4 Answers4

2

You are up a creek: there is no way to initialize any member of a union, other than the first, in C90.

However, C90 requires that any function pointer can be cast to any other function pointer type and back without loss of information. So I think your least-bad available option is

typedef void (*generic_fptr)(void);

struct multi_fn
{
    generic_fptr func_ptr;
    enum { FUNC_TYPE1, FUNC_TYPE2 } func_type;
};

void type1func(float);
void type2func(unsigned char*);

static const struct multi_fn the_funcs[] = {
    { (generic_fptr)type1func, FUNC_TYPE1 },
    { (generic_fptr)type2func, FUNC_TYPE2 }
};
#define CALL_FUNC_TYPE1(ptr, arg) (((void (*)(float)) (ptr))(arg))
#define CALL_FUNC_TYPE2(ptr, arg) (((void (*)(unsigned char *)) (ptr))(arg))

and then

if (the_funcs[i].func_type == FUNC_TYPE1)
    CALL_FUNC_TYPE1(the_funcs[i].func_ptr, float_argument);
else if (the_funcs[i].func_type == FUNC_TYPE2)
    CALL_FUNC_TYPE1(the_funcs[i].func_ptr, unsigned_char_star_argument);
else
    abort();

Sprinkle with further macros and/or helper functions as necessary to make larger-scale control flow readable.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • Casting any function pointer to another function pointer type is legal, strictly conforming, standard C90. – The Paramagnetic Croissant May 20 '14 at 20:15
  • No, **you** are mistaken. Read section 3.3.4 (in the very same document). "A pointer to a function of one type may be converted to a pointer to a function of another type and back again". (By the way, this is a well-known fact; [see this question and answer, for example](http://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type/559671#559671). I am highly surprised as to why you didn't know about this.) – The Paramagnetic Croissant May 21 '14 at 04:32
  • @user3477950 I no longer have C90 as memorized as I used to, and I was searching for the wrong keywords. Thanks for setting me straight. – zwol May 23 '14 at 15:53
1

How to conveniently define...

Sorry, but in C89 and C90, there's no way to initialize a member of a union other than its first member. You won't be able to do this using initialization syntax, you will need explicit assignments.

the best way to do this would be to use a struct instead of an union

No, it won't, because what you conceptually need is a union, and if you use a structure, you will end up confusing other programmers reading your code.

0

You do NOT, strictly speaking, need the "{ .type=...}".

This compiles fine:

struct multi_fn {
        union {
           void (*type1)(float);
           void (*type2)(unsigned char*);
        } func_ptr;
        enum { 
           FUNC_TYPE1, FUNC_TYPE2 
        } func_type;
};

void type1func(float);
void type2func(unsigned char*);

static const struct multi_fn the_funcs[] = {
        { type1func, FUNC_TYPE1 },
        { type2func, FUNC_TYPE2 }
};

This will compile and execute just fine, and it still expresses the idea that the function being pointed to could have any of several signatures.

The only thing you need to remember is to cast to the correct function, based on the value of func_type.

FoggyDay
  • 11,962
  • 4
  • 34
  • 48
-1

You can replace initialization with assignment.

static struct multi_fn the_funcs[2];

void init_funcs(void) {
    the_funcs[0].func_ptr.type1 = type1func;
    the_funcs[0].func_type = FUNC_TYPE1;
    the_funcs[1].func_ptr.type2 = type2func;
    the_funcs[1].func_type = FUNC_TYPE2;
};

Make sure to call init_funcs soon enough.

You can also use macros to build init_funcs, so it would save repetition and look more like initializtion. Something like this (I leave the macro definition as an exercise):

void init_funcs(void) {
    INIT_FUNC(type1, FUNC_TYPE1, type1func);
    INIT_FUNC(type2, FUNC_TYPE2, type2func);
}
ugoren
  • 16,023
  • 3
  • 35
  • 65
  • I'm afraid this code won't compile. The objects inside the array are `const`, which means they can only be initialized, not assigned to at runtime. – Daniel Kamil Kozar May 20 '14 at 21:23
  • @DanielKamilKozar, you're right. But you can remove the `const`. – ugoren May 21 '14 at 06:13
  • Removing the `const` would be conflicting with the idea of having this array at all, mostly because of missed optimization opportunities - which is why I am strongly against it. :) – Daniel Kamil Kozar May 21 '14 at 07:30