C++ 泛型编程之静态断言 gaunthan Posted on Jul 30 2019 ? C++ Generic Programming ? ? C++ ? ## 概述 我们都知道 `assert` 是运行期断言,在契约式编程中常常用来保护入口和出口条件,保证约束条件被满足。 ## 一个例子 假设我们想实现安全的二进制转换函数 safe_reinterpret_cast,它能够保证被转换类型的字节数小于或等于目标类型,即目标类型有足够的内存存放被转换类型的内容: ```cpp template<typename To, typename From> To safe_reinterpret_cast(From from) { assert(sizeof(From) <= sizeof(To)); return (To)from; } ``` 我们希望错误能够在编译期被检测出来,然而使用 assert 并不能达到这个目的。显然我们需要一种编译期断言机制,能够让我们在编译时就执行断言。 你或许会好奇为什么 return 语句那里不使用 `reinterpret_cast<To>(from)`,原因是这个模板设施本身便有这种检查(在 C++11 中),因此使用它将显得我们的 safe_reinterpret_cast 没什么用处。实际上,我们真正想探讨的是如何实现静态断言。 ## 利用数组大小约束实现静态断言 我们知道在 C/C++ 中,数组的大小应该大于零(C++11 允许数组的大小为零)。将数组的大小分别置为非负数和负数,可以引起不报错和报错这两种行为。因此,可以编写下面这样的宏断言: ```cpp #define STATIC_ASSERT(expr) { char unname[(expr) ? 1 : -1]; } ``` 然后用这个宏替换 assert,就能够实现静态断言。但是这种方式有一个很明显的缺点——收到的错误信息无法表达正确信息:  ## 利用名称有意义的模板实现静态断言 更好的方式是依赖一个名称有意义的模板,因为编译器将在错误消息中指出模板的名称: ```cpp template<bool> struct CompileTimeChecker { CompileTimeChecker(...) { } }; template<> struct CompileTimeChecker<false> { }; #define STATIC_ASSERT(expr, msg) { \ class ERROR_##msg {}; \ auto obj = CompileTimeChecker<(expr)>(ERROR_##msg());\ (void)sizeof(obj);\ } ``` 错误信息如下图所示:  这种方式比前面一种可读性要高那么一点,但仍然很难阅读。而且由于不同编译器的出错提示不一样,在某些编译器上出错信息更加复杂和难懂。说到底,我们需要规范的静态断言定义,而这就需要语言标准添加这一定义了。 ## 使用 C++11 static_assert 实际上,C++11 添加了 `static_assert` 表达式,使用它便可以达到静态断言的目的。可以直接将 assert 替换为 static_assert,而且可以精确指定出错信息 :  ## References - Andrei Alexandrescu, 侯捷, 於春景. C++ 设计新思维 [M]. 华中科技大学出版社, 2003. 赏 Wechat Pay Alipay C++ 模板类型推导 ORB-SLAM: A Versatile and Accurate Monocular SLAM System