Runtime cast of void pointer

125 Views Asked by At

I'm writing a C program that applies a different type of quantization to an array depending on an environmental variable.

The problem is that, I need to use the same function void foo(void* ptr, quant_t type, ...) to do things on ptr, but I need to cast it beforehand to the correct type.

(quant_t is an enum type object)

I've tried to do

void foo(void* ptr, quant_t type, ...){
switch(type){
case UNIF:{
struct unif* my_ptr = (struct unif*) ptr;
break;
}
case KMEANS:{
struct kmeans* my_ptr = (struct kmeans*) ptr;
break;
}...}

my_ptr->a = bar(...);
my_ptr->b = baz(...);
}

But it does not work because the declaration of my_ptr is inside the scope of a switch case.

So I've tried to do something like this:

void foo(void* ptr, quant_t type, ...){
void* my_ptr = NULL;
switch(type){
case UNIF:{
my_ptr = (struct unif*) ptr;
break;
}
case KMEANS:{
my_ptr = (struct kmeans*) ptr;
break;
}...}

my_ptr->a = bar(...);
my_ptr->b = baz(...);
}

But it still does not work.

2

There are 2 best solutions below

2
ikegami On BEST ANSWER

For ->a to work, the compiler must know the location (among other things) of the field relative to the pointer. This offset must be constant in C.

We can make this work by making your two types compatible with a third, and using that third type (Base).

typedef struct {
   A a;
   B b;
} Base;

typedef struct {
   Base base;
   …
} Unif;

typedef struct {
   Base base;
   …
} Kmeans;
// `base` doesn't need to be the first field of `Unif` and `Kmeans`
// (unless you want to recuperate the orig ptr from a `Base *` at some point).

void foo( Base *ptr, … ) {
   ptr->a = bar( … );
   ptr->b = baz( … );
}

Unif *unif = …;
foo( &unif->base, … );

Kmeans *kmeans = …;
foo( &kmeans->base, … );

It's less safe, but we could also have called it as follows:

// `base` must be the first field of `Unif` and `Kmeans`.

void foo( Base *ptr, … ) {
   ptr->a = bar( … );
   ptr->b = baz( … );
}

Unif *unif = …;
foo( (Base *)unif, … );

Kmeans *kmeans = …;
foo( (Base *)kmeans, … );

Which means we can make a few changes to reduce the code needed in the caller a little.

// `base` must be the first field of `Unif` and `Kmeans`.

void foo( void *ptr_, … ) {
   Base *ptr = ptr_;
   ptr->a = bar( … );
   ptr->b = baz( … );
}

Unif *unif = …;
foo( unif, … );

Kmeans *kmeans = …;
foo( kmeans, … );

The last two are not as safe as the original as they defeat type checks through explicit and implicit casts.

8
arfneto On

But it does not work because the declaration of my_ptr is inside the scope of a switch case.

It does not work, but not just because of the declaration of my_ptr. You can not just forward ...

I will show an example that may or may not do what you want. I am not sure I understand what do you need.Anyway, if you want a dispacther based on a type and a pointer, this one may fit in.
If the functions uses the same type of arguments a simple 5-line VFT works, indexed by the type.

Example

The basics

typedef enum
{
    UNIF,
    KMEANS
} Class_t;

typedef struct
{
    void        (*bar)(const char*, unsigned, ...);
} UNIF_t;

typedef struct
{
    void        (*baz)(const char*, unsigned, ...);
} KMEANS_t;

// the dispatcher
void     foo(Class_t type, void* ptr, unsigned N, ...);

main for this example

For testing:

  • UNIF_t implements bar. The example bar here gets a string for id and (variadic) some int and just displays the values.
  • KMEANS_t implements baz. The example baz here gets a string for id and (variadic) some char* and just displays the values.
int main(void)
{
    UNIF_t   unif   = {.bar=for_unif};
    KMEANS_t kmeans = {.baz=for_kmeans};

    void* p = &unif;
    foo(UNIF, p, 4, 1, 2, 3, 4);
    foo(KMEANS, &kmeans, 3, "Stack", "Overflow", "C");

    p = &kmeans;
    foo(KMEANS, p, 2, "What", "if?");
    foo(UNIF, &unif, 8, 8, 7, 6, 5, 4, 3, 2, 1);
    return 0;
}

The logic is trivial:

  • a struct of each kind is created
  • a pointer/type pair is used to call the dispatcher foo()
  • the dispatcher calls the required method

foo()

    void foo(Class_t type, void* ptr, unsigned N, ...)
    {
        va_list parms;
        va_start(parms, N);
        switch (type)
        {
            case UNIF:
            {
                ((UNIF_t*)ptr)->bar("testing for UNIF", N, parms);
                break;
            }
            case KMEANS:
            {
                ((KMEANS_t*)ptr)->baz("testing for KMEANS", N, parms);
                break;
            }
            default:
                fprintf(stderr, "Invalid Type 0x%X!\n", type);
                break;
        };  // switch()
        va_end(parms);
    }

This is sort of a C++ functor. It is written this way here because I suppose that there is more data in the structs. If not a simple VFT indexed by the type works fine.

The only not common thing here is the building of a va_list for use in the inner functions.

output

UNIF: id is 'testing for UNIF', 4 args
        args: (1,2,3,4)

KMEANS: id is 'testing for KMEANS', 3 args
 1: 'Stack'
 2: 'Overflow'
 3: 'C'

KMEANS: id is 'testing for KMEANS', 2 args
 1: 'What'
 2: 'if?'

UNIF: id is 'testing for UNIF', 8 args
        args: (8,7,6,5,4,3,2,1)

full code of the example

#include <stdarg.h>
#include <stdio.h>

typedef enum
{
    UNIF,
    KMEANS
} Class_t;

typedef struct
{
    void (*bar)(const char*, unsigned, ...);
} UNIF_t;

typedef struct
{
    void (*baz)(const char*, unsigned, ...);
} KMEANS_t;

// the dispatcher
void foo(Class_t type, void* ptr, unsigned N, ...);

// functions for testing these 'classes'
void for_unif(const char*, unsigned N, va_list parms);
void for_kmeans(const char*, unsigned, va_list parms);

int main(void)
{
    UNIF_t   unif   = {.bar = for_unif};
    KMEANS_t kmeans = {.baz = for_kmeans};

    void* p = &unif;
    foo(UNIF, p, 4, 1, 2, 3, 4);
    foo(KMEANS, &kmeans, 3, "Stack", "Overflow", "C");

    p = &kmeans;
    foo(KMEANS, p, 2, "What", "if?");
    foo(UNIF, &unif, 8, 8, 7, 6, 5, 4, 3, 2, 1);

    return 0;
}

void for_unif(const char* id, unsigned N, va_list parms)
{
    printf("\nUNIF: id is '%s', %d args\n\targs: (", id, N);
    for (size_t i = 0; i < N - 1; i += 1)
        printf("%d,", va_arg(parms, int));
    printf("%d)\n", va_arg(parms, int));
    return;
}

void for_kmeans(const char* id, unsigned N, va_list parms)
{
    printf("\nKMEANS: id is '%s', %d args\n", id, N);
    for (unsigned i = 0; i < N; i += 1)
        printf("%2u: '%s'\n", 1 + i, va_arg(parms, char*));
    return;
}

void foo(Class_t type, void* ptr, unsigned N, ...)
{
    va_list parms;
    va_start(parms, N);
    switch (type)
    {
        case UNIF:
        {
            ((UNIF_t*)ptr)
                ->bar("testing for UNIF", N, parms);
            break;
        }
        case KMEANS:
        {
            ((KMEANS_t*)ptr)
                ->baz("testing for KMEANS", N, parms);
            break;
        }
        default:
            fprintf(stderr, "Invalid Type 0x%X!\n", type);
            break;
    };  // switch()
    va_end(parms);
}

more from the original question

void foo(void* ptr, quant_t type, ...){
void* my_ptr = NULL;
switch(type){
case UNIF:{
my_ptr = (struct unif*) ptr;
break;
}
case KMEANS:{
my_ptr = (struct kmeans*) ptr;
break;
}...}

my_ptr->a = bar(...);
my_ptr->b = baz(...);
}

if my_ptr is void* there is no meaning in my_ptr->a or my_ptr_b.

Since bar and baz have the same signature, if all you can is in just use my_ptr to call a function inside struct unif or struct kmeans, based on type, it can even be simpler, a one-liner in fact: F[type](descr[type], N, parms); using foo as below:

foo() as a one-liner

void foo(Class_t type, void* ptr, unsigned N, ...)
{
    va_list parms;
    va_start(parms, N);
    // clang-format off
    const char* descr[] = {
        [UNIF]= "type is UNIF",[KMEANS]= "type is KMEANS"};
        void(*F[])(const char* id, unsigned N, va_list parms) = {
        [UNIF]   = ((UNIF_t*)ptr)->bar,
        [KMEANS] = ((KMEANS_t*)ptr)->baz
    };
 
    F[type](descr[type], N, parms);
    va_end(parms);
}

If there are nothing else needed inside the structs and just the method is needed, It is a function table only, indexed by type: it works the same as the example above.

example 2

#include <stdarg.h>
#include <stdio.h>

typedef enum
{
    UNIF,
    KMEANS
} Class_t;

typedef struct
{
    void (*bar)(const char*, unsigned, ...);
} UNIF_t;

typedef struct
{
    void (*baz)(const char*, unsigned, ...);
} KMEANS_t;

// the dispatcher
void foo(Class_t type, void* ptr, unsigned N, ...);

// functions for testing these 'classes'
void for_unif(const char*, unsigned N, va_list parms);
void for_kmeans(const char*, unsigned, va_list parms);

int main(void)
{
    UNIF_t   unif   = {.bar = for_unif};
    KMEANS_t kmeans = {.baz = for_kmeans};

    void* p = &unif;
    foo(UNIF, p, 4, 1, 2, 3, 4);
    foo(KMEANS, &kmeans, 3, "Stack", "Overflow", "C");

    p = &kmeans;
    foo(KMEANS, p, 2, "What", "if?");
    foo(UNIF, &unif, 8, 8, 7, 6, 5, 4, 3, 2, 1);

    return 0;
}

void for_unif(const char* id, unsigned N, va_list parms)
{
    printf("\nUNIF: id is '%s', %d args\n\targs: (", id, N);
    for (size_t i = 0; i < N - 1; i += 1)
        printf("%d,", va_arg(parms, int));
    printf("%d)\n", va_arg(parms, int));
    return;
}

void for_kmeans(const char* id, unsigned N, va_list parms)
{
    printf("\nKMEANS: id is '%s', %d args\n", id, N);
    for (unsigned i = 0; i < N; i += 1)
        printf("%2u: '%s'\n", 1 + i, va_arg(parms, char*));
    return;
}

void foo(Class_t type, void* ptr, unsigned N, ...)
{
    va_list parms;
    va_start(parms, N);
    // clang-format off
    const char* descr[] = {
        [UNIF]= "type is UNIF",[KMEANS]= "type is KMEANS"};
        void(*F[])(const char* id, unsigned N, va_list parms) = {
        [UNIF]   = ((UNIF_t*)ptr)->bar,
        [KMEANS] = ((KMEANS_t*)ptr)->baz
    };
    // clang-format on

    F[type](descr[type], N, parms);
    va_end(parms);
}

https://stackoverflow.com/questions/78081529/runtime-cast-of-void-pointer