static_assert
是c++11添加的新语法,它可以使我们在编译期间检测一些断言条件是否为真,如果不满足条件将会产生一条编译错误信息。
使用静态断言可以提前暴露许多问题到编译阶段,极大的方便了我们对代码的排错,提前将一些bug扼杀在摇篮里。
然而有时候静态断言并不能如我们预期的那样工作,今天就来看看这些“不正常”的情况,我将举两个例子,每个都有一定的代表性。
为什么我的static_assert不工作
基于静态断言可以在编译期触发,我们希望实现一个模板类,类型参数不能是int,如果违反约定则会给出编译错误信息:
template <typename T>
struct Obj {
static_assert(!std::is_same_v<T, int>, "T 不能为 int");
// do sth with a
};
int main() {
Obj<int> *ptr = nullptr;
}
按照预期,这段代码应该触发静态断言导致无法编译,然而实际运行的结果却是:
g++ --version
g++ (GCC) 12.2.0
Copyright © 2022 Free Software Foundation, Inc.
本程序是自由软件;请参看源代码的版权声明。本软件没有任何担保;包括没有适销性和某一专用目的下的适用性担保。
g++ -std=c++20 -Wall -Wextra error.cpp
error.cpp: 在函数‘int main()’中:
error.cpp:10:15: 警告:unused variable ‘ptr’ [-Wunused-variable]
10 | Obj<int> *ptr = nullptr;
| ^~~
事实上除了警告我们ptr没有被使用,程序被正常编译了。换clang是一样的结果。也就是说,static_assert根本没生效。
这不应该啊?我们明明用到了模板,而static_assert作为类的一部分应该也被编译器检测到并被触发才对。
答案就是,static_assert确实没有被触发。
我们先来看看模板类中static_assert在什么时候生效:当需要显式或者隐式实例化这个模板类的时候,编译器就会看见这个静态断言,然后检查断言是否通过。
但我们这里不是有Obj<int> *ptr
吗,这难道不会触发实例化吗?答案在c++的标准里:
Unless a class template specialization has been explicitly instantiated (17.7.2) or explicitly specialized (17.7.3), the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program. -- C++17 standard §17.7.1
意思是说,除了显式实例化,模板类还会在需要它实例化的上下文里被隐式实例化,重点在于那个a completely-defined object type
。
这个“完整的对象类型”是什么呢?很简单,就是一个编译器能看到其完整的类型定义的类型,举个例子:
class A;
class B {
int i = 0;
};
这里的B
就是完整的,而A
是不完全类型,一个更为人熟知的称法是:class A
是类A的前置声明。
因为我们没有A的完整定义,所以我们只能声明A*
或者A&
类型的变量或者将A作为函数签名的一部分,但不能A instance
或者new A
。因为前两者是对A
的引用,本身不需要知道完整的A是什么样的,而作为函数签名的一部分的时候并不涉及生成实际需要A的代码,因此也可以使用不完全类型。
所以当你定义一个指针或者引用变量,又或者在写函数或者类方法的签名时,他们并不关心前面的类型,只要这个类型的“名字”是存在的且合法的就行,在这些地方并不会导致模板的实例化。所以静态断言没有被触发。
如何修复这个问题?不使用模板类的指针或者引用可以解决大部分问题,把示例里的Obj<int> *ptr = nullptr
改成Obj<int> ptr;
,立刻就报错了:
g++ -std=c++20 -Wall -Wextra error.cpp
error.cpp: In instantiation of ‘struct Obj<int>’:
error.cpp:12:14: required from here
error.cpp:5:25: 错误:static assertion failed: T 不能为 int
5 | static_assert(!std::is_same_v<T, int>, "T 不能为 int");
| ~~~~~^~~~~~~~~~~~~~~~~
error.cpp:5:25: 附注:‘!(bool)std::is_same_v<int, int>’ eva luates to false
error.cpp: 在函数‘int main()’中:
error.cpp:12:14: 警告:unused variable ‘ptr’ [-Wunused-variable]
12 | Obj<int> ptr;
| ^~~
如果我就要指针呢?那也别用原始指针,请用智能指针:std::unique_ptr<Obj<int>> ptr;
:
g++ -std=c++20 -Wall -Wextra error.cpp
error.cpp: In instantiation of ‘struct Obj<int>’:
/usr/include/c++/12.2.0/bits/unique_ptr.h:93:16: required from ‘void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = Obj<int>]’
/usr/include/c++/12.2.0/bits/unique_ptr.h:396:17: required from ‘std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = Obj<int>; _Dp = std::default_delete<Obj<int> >]’
error.cpp:13:28: required from here
error.cpp:6:25: 错误:static assertion failed: T 不能为 int
6 | static_assert(!std::is_same_v<T, int>, "T 不能为 int");
| ~~~~~^~~~~~~~~~~~~~~~~
error.cpp: