可以通过一些技巧来实现这一点。给定
typedef enum
{
BLUE,
RED
} color_t;
然后定义一个虚拟联合,它不会被调用者使用,但包含与枚举常量同名的成员:
typedef union
{
color_t BLUE;
color_t RED;
} typesafe_color_t;
这是可能的,因为枚举常量和成员/变量名称位于不同的命名空间中。
然后制作一些类似函数的宏:
#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))
然后像这样调用这些宏:
color_t color;
color_assign(color, BLUE);
解释:
- C11
_Generic 关键字确保枚举变量的类型正确。但是,这不能用于枚举常量 BLUE,因为它的类型是 int。
- 因此,辅助宏
c_assign 创建了一个虚拟联合的临时实例,其中指定的初始化语法用于将值BLUE 分配给名为BLUE 的联合成员。如果不存在这样的成员,则代码将无法编译。
- 然后将相应类型的联合成员复制到枚举变量中。
我们实际上不需要辅助宏,我只是将表达式拆分为可读性。写起来也一样好用
#define color_assign(var, val) _Generic((var), \
color_t: (var) = (typesafe_color_t){ .val = val }.val )
例子:
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
编辑
显然以上内容并不能阻止调用者简单地输入color = garbage;。如果您希望完全阻止使用这种枚举分配的可能性,您可以将它放在一个结构中并使用带有 "opaque type" 的私有封装的标准过程:
颜色.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) \
)
颜色.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);