Home > Software design >  How to simply access data in a union inside of a struct?
How to simply access data in a union inside of a struct?

Time:02-06

Here is a type I declared:
(I declared t_sphere, t_cylinder and t_triangle too)

typedef struct  s_intersection{
  double       t1;
  double       t2;
  int id;
  union {
    t_sphere sph;
    t_cylinder cyl;
    t_triangle tri;
  } u;
} t_intersection;

When I use that intersection structure in some code, is there a handy way to refer to the member inside my union ?
e.g. Let's say I want to write a function that acts differently according to the type of geometric_figure it contains. Will I have to do it like this ?

if (geometric_figure_id == SPHERE_ID)
    // I know I will have to refer to p->u with p->u.sph...
else if(geometric_figure_id == CYLINDER_ID)
    // I know I will have to refer to p->u with p->u.cyl...
else if (geometric_figure_id == TRIANGLE_ID)
    // I know I will have to refer to p->u with p->u.tri... 

What if I had 10 different geometric_figures types inside my union ? This feels very heavy.
Do you have a more elegant solution ?

CodePudding user response:

Let's say I want to write a function that acts differently according to the type of geometric_figure it contains. Will I have to do it like this ?

It sounds like an awareness of other languages' support for runtime polymorphic function dispatch may be part of the context for your question. If so, then it's important to recognize that what you're dispatching on here is not the type of any object, as you typically would in (say) C or Java, but rather the value of an integer.

If you want to follow different control paths for different runtime values of an integer, then there is no alternative to writing a flow-control statement -- generally an if / else if / else or a switch -- that directs control appropriately.*

Now, if you were dispatching on type then C does offer type-generic expressions. For the most part, these make sense to use only inside a macro:

#define VOLUME(x) _Generic((x), \
    t_sphere: 4 * PI * (x).r * (x).r * (x).r / 3, \
    t_cylinder: PI * (x).r * (x).r * (x).h, \
    t_cube: (x).edge * (x).edge * (x).edge \
)

In contexts other than a macro, you know the type involved already, so a type-generic expression gains you nothing worth having.

Depending on how much macro magic you want to apply, there are ways avoid writing out long if / else if / else statements or long switch statements by hand. Type-generic macros would likely play a role in something like that. But such a course of action is difficult to implement well. You're more likely to end up with a confusing maintenance nightmare of complex macro stacks than with something that would compare favorably to manually-written switch statements.


*Or maybe a complex expression with nested use of the ternary operator could be taken as fullfilling that need, but using such an expression is not realistic if it needs to be written and maintained by hand.

CodePudding user response:

Do you have a more elegant solution ?

Saying "more elegant" makes it opinion based. So instead of "more elegant" solution, I'll rather call this an alternative solution.

To avoid the many nested if-statements (or a big switch-statement), you can add a function pointer. So when you make an instance of t_intersection, you also set the function pointer to point to the function needed for the specific sub-type of t_intersection.

Here is an example based on OPs code with a few modifications to make it simpler.

#include <stdio.h>

#define SPHERE_ID 0
#define CYLINDER_ID 1

double sph_calculation(void* p)
{
    puts("sph_calculation");
    int n = *(int*)p;        // Cast back to correct type
    double res = 1.0/n;
    return res;
}

double cyl_calculation(void* p)
{
    puts("cyl_calculation");
    float f = *(float*)p;      // Cast back to correct type
    double res = 1.0/f;
    return res;
}

typedef struct  s_intersection{
  double       t1;
  double       t2;
  int id;
  double (*calculation)(void*);   // Function pointer
  union {
    int sph;
    float cyl;
  } u;
} t_intersection;


int main(void)
{
    t_intersection m[2];

    m[0].id = SPHERE_ID;
    m[0].calculation = sph_calculation;     // Set function to be called
    m[0].u.sph = 2;

    m[1].id = CYLINDER_ID;
    m[1].calculation = cyl_calculation;     // Set function to be called
    m[1].u.cyl = 3.0;
    
    // Do the calculation for all types without a need for nested if-statements
    for (int i = 0; i < 2;   i)
    {
        double x = m[i].calculation(&m[i].u);
        printf("%f\n", x);
    }
    
    return 0;
}

Output:

sph_calculation
0.500000
cyl_calculation
0.333333
  • Related