News Hacker|极客洞察

24 182 天前 raphgl.github.io
🤔在 C 中做类型安全泛型:preprocessor vs _Generic vs C++ 模板
真的要用 preprocessor 把 C 变成 C ++ 吗?

🎯 讨论背景

讨论基于一篇展示如何在 C 中实现“类型安全泛型”的文章,实际实现主要依赖 C preprocessor(C 的宏替换与条件编译)生成针对不同类型的函数变体。评论者把焦点放在可读性与工程实践上:有人建议用 C11 的 _Generic(语言内建的类型选择)或直接用 C++ 的 templates 来避免宏的复杂拼接,也有人推荐通过元编程工具(如 poof、ctemplates)或用 Nim 的 metaprogramming 转译到 C 的方法。讨论还扩展到可实施性问题——如何通过编码规范、编译器开关或静态分析强制限制语言特性——以及语言本身的安全性权衡(所谓的“footguns”)。评论假设读者理解 C 的宏与内存安全问题、C++ 特性的复杂性及元编程/转译作为常见替代策略。

📌 讨论焦点

批评:滥用预处理器,可读性差,优先考虑 _Generic

多位评论者指出原文实际上是用 C preprocessor 模拟泛型,认为标题应明确反映这一点。有人回忆在 2004 年曾在生产环境使用过类似做法但发现“滥用预处理器”使代码几乎不可读,从可维护性角度不可取。评论建议采用 C11 的 _Generic 来实现更可读的类型选择机制,尽管也承认 _Generic 自身存在局限和问题。总体观点是:若能使用语言内建特性,应优先于宏拼凑。

[来源1] [来源2]

实用替代与元编程:ctemplates、poof、Nim 与宏封装技巧

也有评论者分享了实战经验并推荐替代方案:一位作者提供了 ctemplates 的实现并在爱好项目中长期使用,说明预处理器技巧在某些场景是可行的。另一位作者提供了自己写的 metaprogramming 工具 poof(生成 C 代码的元编程语言)作为生成泛型代码的替代方案。还有人建议用 Nim 的 metaprogramming 把代码 transpile 到 C,或用 per-function macro wrapper(例如 Vector_New(int)(&v) 展开为 Vector_New_int(&v))来获得更像模板的调用语法。评论强调不同项目对可读性、编译器兼容性和工程复杂度的权衡。

[来源1] [来源2] [来源3] [来源4]

直接用 C++ 模板的建议与关于特性限制、'footguns' 的争论

部分评论建议直接使用 C++ 编译器和 templates 来获得泛型支持,同时通过编码规范限制类等高级特性以保持 C 风格。讨论延伸出如何强制限制:有人询问是否可通过编译器开关或静态分析工具禁止特定 C++ 特性以在代码审查之外自动阻断违规用法。反对者警告一旦引入模板很容易被继承、operator overloading、智能指针等特性吸引,且指出 C++ 带来的隐性陷阱(如对象构造/析构、全局初始化问题)比纯 C 的“脚坑”复杂得多。整场争论还触及语言选择的工程哲学:有的人宁可选 Java/C#/Go 等更受限但更安全的高级语言,也有人认为对团队纪律的要求是可行的折衷。

[来源1] [来源2] [来源3] [来源4] [来源5] [来源6] [来源7] [来源8] [来源9]

📚 术语解释

C preprocessor: C 语言的宏替换与条件编译阶段,用于生成或改写源代码;评论中常被用来手工生成不同类型的函数变体,但易导致可读性和维护性问题。

_Generic: _Generic(C11 引入)是一种编译期的类型选择表达式,可根据操作数类型选择不同实现分支,是比纯宏更可读的实现泛型的语言特性。

templates: templates(C++ 的模板机制)允许在编译期间生成类型特化代码以实现泛型,功能强大但会引入复杂性、特性依赖和潜在的安全/维护隐患(即 'footguns')。

metaprogramming: metaprogramming(元编程)指在编译或构建阶段生成或变换代码的技术,评论提到的 poof、ctemplates、以及 Nim 的宏系统都属于这类替代方案。

transpile: transpile(转译)指将一种语言的源代码转换为另一种语言的等价代码,例如将 Nim 或其他元语言转译为 C,以复用 C 的编译链。