4

I am currently working with a C program that uses structs composed of xyz coordinates, but sometimes these coordinate may refer to vectors (Force/velocity type, not the data structure) while at other times it may be referring to position. I know its possible to simply use one struct for all of these different conditions since they all use mostly the same data type (float), but to simply keep my math better organized (plus variable and struct names) and keep things from getting mixed up, is there a way to define a base struct that defines itself as having three floats, be inherited by another struct that more specifically defines what the struct is supposed to be (such as position instead of velocity, etc. etc.)? I know C is not OOP, but it seems like it could be possible to do this Here is what the base struct would be like:

struct ThreeDCartesianData
{
   float x;
   float y;
   float z;
};

A more specific struct would inherit from that and perhaps define extra variables, or use different names for the variables. There will be multiple position structs being used, but I think only one velocity struct for each set of data. I have seen similar questions to this, but they all seem to be referring to a higher level language (C++, C#, etc. etc.)

cluemein
  • 880
  • 12
  • 27
  • 2
    Do any of these help? [How can I simulate OO-style polymorphism in C?](https://stackoverflow.com/questions/524033/how-can-i-simulate-oo-style-polymorphism-in-c), [Object-orientation in C](https://stackoverflow.com/questions/415452/object-orientation-in-c), [Can you write object-oriented code in C?](https://stackoverflow.com/questions/351733/can-you-write-object-oriented-code-in-c) – kaylum Jan 13 '16 at 21:43
  • Why not use two `typedef`s? – dwanderson Jan 13 '16 at 21:45
  • My C is a little rusty, been working with Java a lot, with the exception of working with a lot of structs of the same type and math functions. Other than that, my experience with C is only based on my previous experience with C++ 3 years ago. Basically, some of these things being described are unfamilliar to me. I know typedef is used in C++ as well, but I don't remember using it often if at all. – cluemein Jan 13 '16 at 21:47
  • @kaylum they help a little, but I'm being a bit more specific here in what I am asking. The answers do help though. – cluemein Jan 13 '16 at 21:50
  • I'm really liking both answers so far. They both answer my question and with different methods. How do I choose one as the best answer if I like them both? One answers the question perfectly, the other is a good alternative to what I am trying to do. – cluemein Jan 13 '16 at 21:52
  • If you were using a *single* `float` would you have this naming problem, and want to `typedef` other flavours of `float`? I can't see the problem: make your code clear from the variable **names** you choose (as ever). – Weather Vane Jan 13 '16 at 21:53
  • Just upvote both of them and then accept the one that applies best to you or that you found most useful. I don't think there is a way to accept more than one answer. – kaylum Jan 13 '16 at 21:55
  • @WeatherVane `typedef`s exist for the reason of additional clarity; I have definitely seen `typedef`s used for a variable type like `float` before, and would argue that it (along with good variable names, first and foremost, of course), increase readability substantially. Most of the time. – dwanderson Jan 13 '16 at 22:05
  • @dwanderson yes I have seen `float` typedeffed not to distinguish it from other `float` types, but to make the implementation of that particular item flexible, say if should you want later to use `double` instead. – Weather Vane Jan 13 '16 at 22:09

3 Answers3

3

You can use a union for this. Your main struct would contain a union of the "derived" structs as well as a "flag" field telling you which member of the union is valid:

enum { DERIVED11, DERIVED2, DERIVED3 };

struct derived1 { int x1; };
struct derived2 { char x2; };
struct derived3 { float x3; };

struct ThreeDCartesianData
{
   float x;
   float y;
   float z;
   int derivedType;
   union {
     struct derived1 d1;
     struct derived2 d2;
     struct derived3 d3;
   } derived;
};

Then you can use them like this:

struct ThreeDCartesianData data1;
data1.x=0;
data1.y=0;
data1.z=0;
data1.derivedType = DERIVED1;
data1.derived.d1.x1 = 4;

You could alternately define them like this:

struct common
{
   int type;
   float x;
   float y;
   float z;
};
struct derived1
{
   int type;
   float x;
   float y;
   float z;
   int x1;
};
struct derived2
{
   int type;
   float x;
   float y;
   float z;
   char x2;
};
struct derived3
{
   int type;
   float x;
   float y;
   float z;
   float x3;
};

union ThreeDCartesianData {
     struct common c;
     struct derived1 d1;
     struct derived2 d2;
     struct derived3 d3;
};    

And use them like this:

union ThreeDCartesianData data1;
data1.c.type=DERIVED1;
data1.d1.x=0;
data1.d1.y=0;
data1.d1.z=0;
data1.d1.x1 = 4;

If all the structs in a union have an initial list elements of the same type in the same order, the standard allows you to access those fields from any of the sub-structs safely.

dbush
  • 162,826
  • 18
  • 167
  • 209
  • Is that basically saying you have a struct that inherits from the main struct that, in addition to having x, y, and z, it has another variable called x1? – cluemein Jan 13 '16 at 21:45
  • 1
    You have a main struct containing the common fields as well as a union of the derived structs. `x1` is a member of one of the derived structs. – dbush Jan 13 '16 at 21:54
  • So its more like merging two different structs than one inheriting the other? But has the same effect? – cluemein Jan 13 '16 at 21:55
  • @cluemein Basically, yes. – dbush Jan 13 '16 at 22:02
3

What about using typedefs?

typedef struct general3d {
    float x;
    float y;
    float z;
} general3d_t;

typedef general3d position;
typedef general3d velocity;

This way, when you come across something that's a velocity type, you encode that into the variable type, but under the hood, it's still just 3 points, x, y, z. Then, readers of the code will know you're talking about a velocity and not a position. If you want to get really crazy with it, you hide general3d away in some implementation file, so a user can never instantiate a general3d on their own, since they should be using either position or velocity as the situation requires; this may or may not be reasonable for your task at hand/worth the extra effort.

EDIT: I'm not positive about variable-renaming or about adding more variables directly to the same struct, but I would start to head in the direction of a different design at that point.

On the one hand, if you have two structs that have the same underlying types but require different names, you probably just have two separate structs. For example:

struct point3d {
    float x;
    float y;
    float z;
};

struct person {
    float age;
    float weight;
    float salary;
};

Yes, those are both 3 floats, but their understanding is very different, and they should be able to vary on their own if one or the other changes. Perhaps I want to add a name field to the person, but there's no reasonable analogue for a char * on point3d. Just define them separately if they mean different things.

As for adding more variables, that sounds like structs that contain other structs:

struct point3d {
    float x;
    float y;
    float z;
};

struct person {
    point3d position;
    float age;
    float weight;
    float salary;
};

// access like:
person.position.x;
dwanderson
  • 2,345
  • 1
  • 19
  • 35
  • It's not obvious how this answers this part of the question from the OP: "A more specific struct would inherit from that and perhaps define extra variables, or use different names for the variables." – kaylum Jan 13 '16 at 21:51
  • Ah, yeah, fair point. I'll work on that and edit; my offhand thought is "don't think you can do that" but I'd rather double-check before posting a negative assertion, and if someone beats me to the solution, then at least I didn't post mistakes. – dwanderson Jan 13 '16 at 21:52
  • The real issue the OP was facing seems to be clarity of code without boilerplate, at least that is how I interpreted. – SGM1 Jan 13 '16 at 21:53
  • 1
    Right, so I figure `typedef`s solve most of that. Different variable names is just starting to sound like two different `struct`s that happen to have the same types - define them separately to keep it clear,; and more variables is sounding like `struct`s containing other `struct`s. – dwanderson Jan 13 '16 at 21:55
  • 1
    Not to be a nit, but in your first code block, the `typedef`s appear to be reversed [due to a typo]. Don't you want (e.g.) `typedef general3d position` instead of `typedef position general3d`? And, shouldn't the top line of that block be preceeded by `typedef` (e.g. `typedef struct general3d ...`)? – Craig Estey Jan 13 '16 at 23:15
  • 1
    @CraigEstey probably definitely correct - I was just writing off the top of my head, and didn't have an IDE/compiler handy. And don't worry about nit-picking - compilers are pretty nit-picky themselves and having incorrect info on SO doesn't help anyway. Thanks! – dwanderson Jan 14 '16 at 15:12
  • 1
    It was obvious from your post/answer that you knew what you were doing. I only mentioned the typo in the interests of posterity. A few weeks back, I noticed an answer that got a code example typo fixed by its author and the post was three years old ... – Craig Estey Jan 14 '16 at 22:38
1

I've done this sort of thing before. I embed a copy of the base type at the front of the derived type struct. This more closely mimics what c++ might do. Here are two methods I've used.


Using simple type:

#define XYZDEF \
    int type; \
    float x; \
    float y; \
    float z

// base type
struct xyzdata {
    XYZDEF;
};

// derived type 1
struct vector {
    XYZDEF;
    int vector_info;
    ...
};

// derived type 2
struct position {
    XYZDEF;
    int position_info;
    ...
};

#define BASEOF(_ptr) \
    ((struct xyzdata *) (_ptr))

// vector_rotate -- rotate a vector
void
vector_rotate(vector *ptr)
{
}

// position_rotate -- rotate a position
void
position_rotate(position *ptr)
{
}

// xyzrotate -- rotate
void
xyzrotate(xyzdata *ptr)
{

    switch (ptr->type) {
    case TYPE_POSITION:
        vector_rotate((vector *) ptr);
        break;
    case TYPE_VECTOR:
        position_rotate((position *) ptr);
        break;
    }
}

Using a virtual function table pointer:

#define XYZDEF \
    int type; \
    vtbl *vtbl; \
    float x; \
    float y; \
    float z

// forward definitions
struct xyzdata;
struct vector;
struct position;

// virtual function table
struct vtbl {
    void (*rotate)(struct xyzdata *);
};

// base type
struct xyzdata {
    XYZDEF;
};

// derived type 1
struct vector {
    XYZDEF;
    int vector_info;
    ...
};

// derived type 2
struct position {
    XYZDEF;
    int position_info;
    ...
};

#define BASEOF(_ptr) \
    ((struct xyzdata *) (_ptr))

// vector_rotate -- rotate a vector
void
vector_rotate(struct xyzdata *ptr)
{
    struct vector *vec = (void *) ptr;
    ...
}

// position_rotate -- rotate a position
void
position_rotate(struct xyzdata *ptr)
{
    struct position *pos = (void *) ptr;
    ...
}

// xyzrotate -- rotate
void
xyzrotate(xyzdata *ptr)
{

    ptr->vtbl->rotate(ptr);
}
Craig Estey
  • 22,991
  • 3
  • 18
  • 37