3

How to initialize a union with function pointers without errors or warnings? The code is targeted at embedded and have to compile it both in C and C++.

However I face the problem that direct initializing yields an warning of incompatible pointers with C and an error in C++ while designated initialization has been deprecated in C++.

Is there any way to do this without warnings and errors in C and C++?

Minimal example:

struct List {
    union {
        int (*foo)(int num, int data);
        int (*fee)(int num, float  data);
    };
};

int foo_fun(int pnum, int data);
int fee_fun(int pnum, float  data);

static const struct List list[] = {
{
    {foo_fun},
},

{
    {fee_fun},
/* C = warning: incompatible pointer types initializing 'int (*)(int, int)'
 * with an expression of type 'int (int, float)'
 */
/* C++ = error: cannot initialize a member subobject of type 'int (*)(int, int)'
 * with an lvalue of type 'int (int, float)':
 * type mismatch at 2nd parameter ('int' vs 'float')
 */
},

/* With C++ */
{
    {.fee = fee_fun},
/*   ^^^^^^^^^^^^^
 * C++ = warning: designated initializers are a C99 feature
 */
},

};

The code does work with the warnings incompatible pointer types or designated initializers are a C99 feature.

The crude way is to drop the union and use a void pointer. However, that is far down my list of preferred options due to obvious drawbacks.

Correctly remarked by alinsoar. Making sure the correct function is called is the job of other elements in List currently omitted in the example.


Designated initializes will become fully available again in C++20.
Until then they have no effect. Except for unions where they still seem to work. (minus the warning)

Jeroen3
  • 919
  • 5
  • 20
  • 1
    Designated initializers will be available in C++20 (at least a subset of what exists in C). – Mat Nov 28 '19 at 09:03
  • @Mat I know. https://stackoverflow.com/questions/58559463/embedded-c-static-initialization-of-struct-arrays/58559579#58559579 – Jeroen3 Nov 28 '19 at 09:07
  • afaik designated initalization has not been deprecated in c++, it never was in c++ but rather it got introduce in c after c++ came into existance – 463035818_is_not_an_ai Nov 28 '19 at 09:10
  • Probably if you write `{.fee=fee_fun}`, instead of `{fee_fun}`, it will work with no warning in C. – alinsoar Nov 28 '19 at 09:17
  • anyway, you need to keep the tag of the type of the function, otherwise you will not be able to dispach between the float and int functions. – alinsoar Nov 28 '19 at 09:18
  • 1
    What about writing a union constructor wrapped in `#ifdef __cplusplus`? There is no other way to initialize anything but the first union member in C++. Edit: Well, there's also no other way to initialize a C99 union member other than the first without designated initializers... – Max Langhof Nov 28 '19 at 09:25
  • 1
    A union is an ugly hack. A cast is another ugly hack. I don't know why prefer one ugly hack over another. – n. m. could be an AI Nov 28 '19 at 15:20

2 Answers2

2

The only way to initialize a union member beyond the first in C++ (before C++20) is a constructor in the union.

The only way to initialize a union member beyond the first in C is a designated initializer.

This doesn't leave a whole lot of wiggle room. Beware, ugliness ahead:

// For convenience
typedef int (*fooPtr)(int, int);
typedef int (*feePtr)(int, float);


#ifndef __cplusplus
#define INITIALIZE(x) .x =
#else
#define INITIALIZE(x)
#endif


struct List {
    union X {
#ifdef __cplusplus
        constexpr X(fooPtr foo_) : foo(foo_) {}
        constexpr X(feePtr fee_) : fee(fee_) {}
#endif
        fooPtr foo;
        feePtr fee;
    } x;
};

int foo_fun(int pnum, int data);
int fee_fun(int pnum, float  data);

static const struct List list[] = {
    {
        {INITIALIZE(foo) foo_fun},
    },
    {
        {INITIALIZE(fee) fee_fun},
    },
};

https://godbolt.org/z/pd42HT

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • @Jeroen3 Wait, which compiler does it not work in? ARM gcc 8.2 seems happy: https://godbolt.org/z/g2EUbQ. So is ARMclang: https://godbolt.org/z/K9J8dD (to be clear, I know nothing about ARM). – Max Langhof Nov 28 '19 at 12:24
  • I have armcc v5.06 bundles with uVision 4. It errors similar to C++ and requires the designated initializers. I'm afraid I can't use the constructor method. Since I have to load it into flash, it must be const. It's an embedded target. – Jeroen3 Nov 28 '19 at 14:36
  • @Jeroen3 I don't understand. Is your compiler a C compiler or a C++ compiler? Or both? I assume the compiler is/you are compiling the code as C, not C++ ? In that case, you should get the designated initializers from `INITIALIZE`... – Max Langhof Nov 28 '19 at 14:45
  • 1
    Regarding constructors: Mark them `constexpr` and you'll get the list written at compile-time: https://godbolt.org/z/pd42HT (answer updated). – Max Langhof Nov 28 '19 at 14:52
  • This works with --c99 given to armcc 5.06, and with mingw32-730 C and C++. – Jeroen3 Nov 29 '19 at 06:50
0

Not in pretty ways, but there's some possibilities. C++ has constructor overloading. C has _Generic and designated initializers.

typedef int foo_t (int num, int data);
typedef int fee_t (int num, float data);

typedef struct List
{
  union 
  {
    foo_t* foo;
    fee_t* fee;
  };

  #ifdef __cplusplus
    List(foo_t* f) :foo(f){}
    List(fee_t* f) :fee(f){}  

    #define FOO_INIT(f) List(f)
    #define FEE_INIT(f) List(f)
  #else
    #define FOO_INIT(f) { .foo = _Generic((f), foo_t*: (f)) }
    #define FEE_INIT(f) { .fee = _Generic((f), fee_t*: (f)) }
  #endif   
} List;

int the_foo (int num, int data){ return 0;}
int the_fee (int num, float data){ return 0;}

int main (void)
{
  List list_foo = FOO_INIT(the_foo);
  List list_fee = FEE_INIT(the_fee);
  return 0;
}

In either language this should be type safe.

Lundin
  • 195,001
  • 40
  • 254
  • 396