据我了解,在 C 中实现通用数据类型的标准方法是使用 void 指针。然而,另一种方法是使用宏。这是使用宏的通用“Option”类型的示例实现:
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#define OPTION(T) \
struct option_##T { \
bool exists; \
T data; \
}; \
typedef struct option_##T option_##T; \
static inline bool is_some_##T(option_##T x) { return x.exists; } \
static inline bool is_none_##T(option_##T x) { return !x.exists; }
#define is_some(x) _Generic((x), option_int: is_some_int, option_char: is_some_char)(x)
#define is_none(x) _Generic((x), option_int: is_none_int, option_char: is_none_char)(x)
OPTION(int);
OPTION(char);
int main(int argc, char *argv[]) {
option_int x = {.exists = true, .data=3};
option_char y = {.exists = true, .data='a'};
printf("x is some: %d\n", is_some(x));
printf("y is some: %d\n", is_some(y));
return 0;
}
另一种方法是使用 void * 进行数据输入。然而,这会增加一个额外的间接层(同时可能节省二进制大小)。 void * 方法更常见有什么原因吗?
因为
_Generic
是一个相对较新的功能。 “老派”的方法总是使用空指针,或者通过创建一个与示例类似的结构,但通常使用枚举来标记所表示的类型。或者通过特定类型的回调,如众所周知的 bsearch
/qsort
.
至于
_Generic
和即将标准化的typeof
,有很多不同的使用方式。实际上也不需要包装器结构/枚举。例如,您可以将它们用作“穷人的模板” - 也许不推荐这样做,但功能非常强大并且类型安全:
#include <stdio.h>
#define TYPES_SUPPORTED(X) \
X(int, %d) \
X(char, %c) \
#define PRINT(type, fmt) \
void type##_print (type t) \
{ \
printf(#fmt "\n", t); \
}
TYPES_SUPPORTED(PRINT)
#define GENERIC_PRINT(type, fmt) ,type: type##_print
#define print(x) \
_Generic((x), \
default:0 \
TYPES_SUPPORTED(GENERIC_PRINT) )(x)
int main()
{
int a = 123;
char c = 'A';
print(a);
print(c);
}
这种利用“X 宏”的方式意味着您可以将支持的所有类型显示到列表中,无需像示例中那样调用单个宏。相反,您为每个用例创建一个宏,然后调用 X 宏列表。
因此,此示例创建一个函数
int_print(int t)
和一个 char_print(char t)
,并使用正确的格式说明符打印参数。
然后,我们可以从类型通用 print
宏依次调用这些函数,其中
_Generic
关联列表也已烘焙到 X 宏列表中,从而减少了键入每个条件的需要。这里的邪恶技巧是先放置
default:
,然后用
,
开始每个宏扩展,因为
_Generic
与数组/枚举声明不同,初始化列表等不喜欢尾随逗号。