泛型从 C 到 C++ 的演进
C 语言里一直没有真正的泛型,C11 引入的 _Generic 算是给了个折中办法。它能在编译期根据表达式类型选择一个分支,配合宏可以模拟出类型多态的效果。但这种方式很有限,只能做简单的分发,一旦逻辑复杂代码就变得难读。
#define print_value(x) _Generic((x), \
int: printf("%d\n"), \
double: printf("%lf\n"), \
char*: printf("%s\n"))(x)
这段写法在日常小工具里够用,但很难支撑大型系统中的通用组件。C++ 从模板诞生第一天就在解决这个问题,到了 C++17,几个新特性让泛型代码的写法终于不那么折磨人了。
_Generic 与历史包袱
早期 C 标准对代码复用的支持一步步在加:
- C89 只有
void*,完全没类型安全,全靠人工保证转换正确 - C99 有了内联函数和可变宏,封装性好了一些
- C11/C17 把
_Generic标准化,跨编译器的行为也稳定下来
| 标准版本 | 关键特性 | 对泛型支持的影响 |
|---|---|---|
| C89 | void* | 无类型安全,易出错 |
| C99 | 内联函数、复合字面量 | 提高封装性 |
| C11/C17 | _Generic | 实现编译期类型分支 |
graph LR
A[原始数据类型] --> B{使用_Generic 判断}
B -->|int| C[调用 printf%d]
B -->|double| D[调用 printf%lf]
B -->|char*| E[调用 printf%s]
到了 C++ 这边,模板早已是标配,但 C++17 带来的几个改进真正让泛型代码的编写和维护成本降了下来。
C++17 让泛型写法更自然
if constexpr 终结冗长的重载与 SFINAE
if constexpr 在编译期就定下走哪个分支,不符合条件的代码直接丢弃,不会实例化。这比靠 enable_if 和函数重载去挑分支清爽太多。
template <typename T>
auto process(T value) {
if constexpr (std::is_integral_v<T>) {
return value * 2; // 整型才走这里
} else if (std::is_floating_point_v<T>) {
value + ;
} {
(, );
}
}

