Let's suppose you are writing a C struct
which represents a course in a meal. One of the fields in the course struct
is of type:
enum TP_course {STARTER, MAINCOURSE, DESSERT};
Then, depending on the type of the course, you have a subtype:
enum TP_starter {SALAD, GRILLEDVEGETABLES, PASTA};
enum TP_maincourse {BEEF, LAMB, FISH};
enum TP_dessert {APPLEPIE, ICECREAM, MOUSSE};
Given that only one of such enums will be used at a time (depending on the type of the course), it makes sense to aggregate them in a union
:
union U_subtype {
enum TP_starter s;
enum TP_maincourse m;
enum TP_dessert d;
};
So the course struct
would look like this:
struct S_course {
enum TP_course type;
union U_subtype stype;
float price_in_USD;
int availability;
...and all the rest of data would follow...
};
Ok, everything is clear, but... is there any coding strategy I could follow to try to enforce safe access to the stype
tagged union above? Perhaps making it opaque in some way?
For example, if I write a switch/case
block for an enum
and I forget to write a case
for a value, the compiler will trigger a warning, which is of great help for maintaining the code in the future. But if I access stype.s
without first checking if type==STARTER
, the compiler cannot be smart enough for realizing of the risky coding, and won't warn at all.
Can I organize the code in some way so that it's not possible to access the members of the U_subtype
union except in a very limited place where I clearly document how the access to such members must be done?
A tagged union declaration looks just like a C union, except that it you must specify the @tagged qualifier when declaring it. For example: @tagged union Foo { int i; double d; char *@fat s; }; The primary difference with C unions is that a tagged union includes a hidden tag.
We use the union keyword to define unions. Here's an example: union car { char name[50]; int price; }; The above code defines a derived type union car .
A tagged union is a type-checked union. That means you can no longer write to the union using one member type, and read it back using another. Tagged union enforces type checking by inserting additional bits into the union to store how the union was initially accessed.
Type constructors produce a tagged union type, given the initial tag type and the corresponding type. Mathematically, tagged unions correspond to disjoint or discriminated unions, usually written using +. Given an element of a disjoint union A + B, it is possible to determine whether it came from A or B.
You can
.
/* header */
struct S_course; //forward declaration
enum TP_starter {SALAD, GRILLEDVEGETABLES, PASTA};
enum TP_maincourse {BEEF, LAMB, FISH};
enum TP_dessert {APPLEPIE, ICECREAM, MOUSSE};
void S_course__set_starter(struct S_course *this, enum TP_starter starter);
//accessor functions
void S_course__set_maincourse(struct S_course *this, enum TP_maincourse maincourse);
void S_course__set_dessert(struct S_course *this, enum TP_dessert dessert);
/* c file */
enum TP_course {STARTER, MAINCOURSE, DESSERT};
union U_subtype {
enum TP_starter s;
enum TP_maincourse m;
enum TP_dessert d;
};
struct S_course {
enum TP_course type;
union U_subtype stype;
float price_in_USD;
int availability;
/*...*/
};
void S_course__set_starter(struct S_course *this, enum TP_starter starter)
{
this->type = STARTER;
this->stype.s = starter;
}
Use member names that scream don't touch me, or a name like tagged_union
, which should make it obvious how it needs to be accessed.
or
Switch to C++ and use its access control features (private/protected) to hide only some members while allowing access through public member/friend functions
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With