Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create type safe enums?

To achieve type safety with enums in C is problematic, since they are essentially just integers. And enumeration constants are in fact defined to be of type int by the standard.

To achieve a bit of type safety I do tricks with pointers like this:

typedef enum {   BLUE,   RED } color_t;  void color_assign (color_t* var, color_t val)  {    *var = val;  } 

Because pointers have stricter type rules than values, so this prevents code such as this:

int x;  color_assign(&x, BLUE); // compiler error 

But it doesn't prevent code like this:

color_t color; color_assign(&color, 123); // garbage value 

This is because the enumeration constant is essentially just an int and can get implicitly assigned to an enumeration variable.

Is there a way to write such a function or macro color_assign, that can achieve complete type safety even for enumeration constants?

like image 820
Lundin Avatar asked Mar 27 '17 09:03

Lundin


People also ask

Are enum a type-safe?

The enums are type-safe means that an enum has its own namespace, we can't assign any other value other than specified in enum constants. Typesafe enums are introduced in Java 1.5 Version. Additionally, an enum is a reference type, which means that it behaves more like a class or an interface.

What is type-safe enum C#?

The strongly typed enum pattern or the type-safe enum pattern as it is called, can be used to mitigate the design and usage constraints we discussed in the earlier section when working with enums. This pattern makes sure that the type is extensible and you can use it much like an enum.

Is enum type-safe C++?

Enumerations in C++03 are not sufficiently type-safe and may lead to unintended errors. In spite of being a language-supported feature, enums also have code portability issues due to different ways in which different compilers handle the corner cases of enumerations.

Are enums are type-safe Mcq?

Explanation: Enums are type-safe as they have own name-space.


1 Answers

It is possible to achieve this with a few tricks. Given

typedef enum {   BLUE,   RED } color_t; 

Then define a dummy union which won't be used by the caller, but contains members with the same names as the enumeration constants:

typedef union {   color_t BLUE;   color_t RED; } typesafe_color_t; 

This is possible because enumeration constants and member/variable names reside in different namespaces.

Then make some function-like macros:

#define c_assign(var, val) (var) = (typesafe_color_t){ .val = val }.val #define color_assign(var, val) _Generic((var), color_t: c_assign(var, val)) 

These macros are then called like this:

color_t color; color_assign(color, BLUE);  

Explanation:

  • The C11 _Generic keyword ensures that the enumeration variable is of the correct type. However, this can't be used on the enumeration constant BLUE because it is of type int.
  • Therefore the helper macro c_assign creates a temporary instance of the dummy union, where the designated initializer syntax is used to assign the value BLUE to a union member named BLUE. If no such member exists, the code won't compile.
  • The union member of the corresponding type is then copied into the enum variable.

We actually don't need the helper macro, I just split the expression for readability. It works just as fine to write

#define color_assign(var, val) _Generic((var), \ color_t: (var) = (typesafe_color_t){ .val = val }.val ) 

Examples:

color_t color;  color_assign(color, BLUE);// ok color_assign(color, RED); // ok  color_assign(color, 0);   // compiler error   int x; color_assign(x, BLUE);    // compiler error  typedef enum { foo } bar; color_assign(color, foo); // compiler error color_assign(bar, BLUE);  // compiler error 

EDIT

Obviously the above doesn't prevent the caller from simply typing color = garbage;. If you wish to entirely block the possibility of using such assignment of the enum, you can put it in a struct and use the standard procedure of private encapsulation with "opaque type":

color.h

#include <stdlib.h>  typedef enum {   BLUE,   RED } color_t;  typedef union {   color_t BLUE;   color_t RED; } typesafe_color_t;  typedef struct col_t col_t; // opaque type  col_t* col_alloc (void); void   col_free (col_t* col);  void col_assign (col_t* col, color_t color);  #define color_assign(var, val)   \   _Generic( (var),               \     col_t*: col_assign((var), (typesafe_color_t){ .val = val }.val) \   ) 

color.c

#include "color.h"  struct col_t {   color_t color; };  col_t* col_alloc (void)  {    return malloc(sizeof(col_t)); // (needs proper error handling) }  void col_free (col_t* col) {   free(col); }  void col_assign (col_t* col, color_t color) {   col->color = color; } 

main.c

col_t* color; color = col_alloc();  color_assign(color, BLUE);   col_free(color); 
like image 57
Lundin Avatar answered Oct 21 '22 12:10

Lundin