- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
作者: @apocelipes 本文为作者原创,转载请注明出处: https://www.cnblogs.com/apocelipes/p/16910089.html 。
static_assert 是c++11添加的新语法,它可以使我们在编译期间检测一些断言条件是否为真,如果不满足条件将会产生一条编译错误信息.
使用静态断言可以提前暴露许多问题到编译阶段,极大的方便了我们对代码的排错,提前将一些bug扼杀在摇篮里.
然而有时候静态断言并不能如我们预期的那样工作,今天就来看看这些“不正常”的情况,我将举两个例子,每个都有一定的代表性.
基于静态断言可以在编译期触发,我们希望实现一个模板类,类型参数不能是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>’ evaluates 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:6:25: 附注:‘!(bool)std::is_same_v<int, int>’ evaluates to false
模板参数可以是不完整类型,但这里智能指针在析构的时候必须要有完整的类型定义,所以同样触发了类型断言.
这里还有个坑, shared_ptr 可以使用不完整类型,但从原始指针构造 shared_ptr 或者使用它的方法的时候,不接受非完整类型,所以上述代码用 shared_ptr 是不行的.
unique_ptr 虽然也可以使用不完整类型,但必须在智能指针对象被析构的地方可以看到被销毁的类型的完整定义,上面的例子正是在这一步的时候需要类型的完整定义,从而触发隐式实例化,所以触发了静态断言.
如果我一定要用引用呢?通常这没有问题,因为引用需要绑定到一个对象,在函数参数里的话虽然没显式绑定也很可能在函数代码里使用了具体类型的某些方法,这些都需要完整的类型定义,从而触发断言。如果是引用作为类的成员,则必须提供一个构造函数来保证初始化这些引用,所以也没有问题。如果你真的遇到问题了,也可以实现现代c++推崇的值语义和移动语义;否则,你应该思考下自己的设计是否真的合理了.
如果代码里没有实例化模板类的操作(包括显式和隐式)编译器就不会主动去生成模板的实例,是不是可以利用这点来屏蔽某些不需要的模板重载被触发呢?
看个例子:
template <typename T>
struct Wrapper {
// 只接受std::function
static_assert(0, "T must be a std::function");
};
template <typename T, typename... U>
struct Wrapper<std::function<T(U...)>> {
// do sth
};
int f(int i) {
return i*i;
}
int main() {
std::function<int(int)> func{f};
Wrapper<decltype(func)> w;
}
我们的 Wrapper 包装类只接受 std::function ,其他的类型不能正常工作,在c++20的concept出来之前我们只能用元编程的做法来实现类似的功能。上面的代码与SFINAE手法相比既简单有好理解.
但当我们编译程序:
g++ -std=c++20 -Wall -Wextra error.cpp
error.cpp:6:19: 错误:static assertion failed: T must be a std::function
6 | static_assert(0, "T must be a std::function");
| ^
error.cpp: 在函数‘int main()’中:
error.cpp:20:29: 警告:unused variable ‘w’ [-Wunused-variable]
20 | Wrapper<decltype(func)> w;
| ^
静态断言竟然被触发了?明明我们的代码里没有实例化会报错的那个模板的地方啊.
这时候看代码是没什么用的,需要看标准怎么说的:
If no valid specialization can be generated for a template definition, and that template is not instantiated, the template definition is ill-formed, no diagnostic required. 。
如果模板没有被使用,也没有任何针对它的特化或部分特化,且模板内部的代码有错误,编译器并不需要给出诊断信息.
重点在于“no diagnostic required”,它说不需要,但也没禁止,所以检测到模板内部的错误并报错也是正常的,不报错也是正常的,这个甚至不算 undefined behavior .
而且我们的静态断言里所有的内容都能在编译期的初步检查里得到,所以g++和clang++都会产生一条编译错误.
那么怎么解决问题呢?我们要确保模板里的代码至少进行模板类型推导前都是没法判断是否合法的。因此除了没什么语法上明显的错误,我们需要让静态断言依赖模板参数:
template <typename T>
struct Wrapper {
// 只接受std::function
static_assert(sizeof(T) < 0, "T must be a std::function");
};
所有能被sizeof计算的类型大小都不会比0小,所以这个断言总会失败,而且因为我们的断言依赖模板参数,所以除非真的实例化这个模板,否则没法判断代码是不是合法的,因此编译期也不会触发静态断言.
下面是触发断言的结果:
g++ -std=c++20 -Wall -Wextra error.cpp
error.cpp: In instantiation of ‘struct Wrapper<int>’:
error.cpp:20:18: required from here
error.cpp:6:29: 错误:static assertion failed: T must be a std::function
6 | static_assert(sizeof(T) < 0, "T must be a std::function");
| ~~~~~~~~~~^~~
error.cpp:6:29: 附注:the comparison reduces to ‘(4 < 0)’
error.cpp: 在函数‘int main()’中:
error.cpp:20:18: 警告:unused variable ‘w’ [-Wunused-variable]
20 | Wrapper<int> w;
| ^
现在静态断言可以如我们预期的那样工作了.
要想避免static_assert不按我们预期的情况来工作,需要遵守下面的原则:
https://stackoverflow.com/questions/5246049/c11-static-assert-and-template-instantiation 。
https://blog.knatten.org/2018/10/19/static_assert-in-templates/ 。
最后此篇关于为什么你的static_assert不能按预期的工作?的文章就讲到这里了,如果你想了解更多关于为什么你的static_assert不能按预期的工作?的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在尝试创建一个由 std::initializer_list 初始化的示例数组类。我想在编译时检查初始化列表的大小是否不超过数组的大小。由于静态断言只能评估 constexpr,因此不会编译此代码
这是另一个 question 的后续内容 我试图在编译时确定特定实现是否在结构中添加了未命名的填充。像 gcc 这样的特定实现允许使用编译指示来控制结构中的填充和对齐,但代价是与其他实现兼容。由于 C
因此,我正在寻找一种方法,如果用于声明对象的值等于另一个值(不希望使用 C 的 assert 宏),则会导致编译时错误. 是的,我知道为什么会出现这个问题......编译器在他/她提示expressi
是否可以对数组值进行编译时检查? 例子: typedef enum { dummy0 = 0, dummy1, dummy2 } eDummyEnum; typedef str
我的问题是,下面的代码是否有效: template class Class { static_assert(sizeof(i) == 0, "Class instantiated with i
我正在尝试检查参数包中的每个参数是否可以存储在 8 个字节以内(sizeof inline auto invoke(std::uint64_t hash, Arguments... argument
我试图禁止对我已有的类的引用,但我看到了一些奇怪的行为。我已经构建了一个玩具示例来展示正在发生的事情。如果我有这个: template struct something { }; template
我有这样的情况: #define FOO(Readonly) static_assert(Readonly, "Fire!"); Readonly 显然会按字面意思粘贴为“false”或“true”,
我有一个函数模板,它接受一个输出迭代器参数。我如何使用 static_assert 检查实例化是否使用了适当的迭代器? (即,它既是一个输出迭代器,又分配正确类型的元素。) #include #in
在 C++11 中是否有一种优雅的方式来执行条件 static_assert 例如: template class MyClass { COMPILE_TIME_IF( IsTypeBuil
有(不要担心这段代码的长度,专注于struct X和Range)(你可以复制和粘贴并且应该编译): 已编辑 #include #include //This is from file "S
我在编译时过滤一个元组,如果元组为空或者元组包含多个元素,我想输出一条自定义错误消息。 static_assert 似乎有点太复杂了,因为我需要使用 bool 逻辑。 static_assert(si
现代优化 C++ 编译器是否受益于 static_asserts? 例如,如果我断言一个整数只能在一个受限范围内,编译器是通过优化步骤携带该信息还是它仍然只是一个整数? 请不要回答编译器可以考虑它。问
我想做一些static dispatch的工作,让基类static_cast this指针指向派生类并调用同名函数来实现多态。我还想用 static_assert 来确保派生类确实重载了特定功能(否则
我有一个模板函数来操作递归数据结构。在调用的初始函数中,我有以下形式: template auto get(param i) -> int { static_assert(Pos (i.re
我有一个函数模板定义如下: template Test &operator Test &operator::value || std::is_same::valu
我有这段代码来确定模板类型是否有 foo(): template struct has_foo : std::false_type {}; template struct has_foo().fo
我想使用 static_assert 对我的类的配置实现各种限制。早些时候,我只使用一个枚举,并且只允许一个需要所述枚举的构造函数来对我的类实现限制。如果我有类似下面的内容并且范围是从 0 到 4,这
为什么 std 库不使用这些呢?目前,如果对不可复制对象的复制构造函数进行调用,则错误消息可能有点“神秘”或让以前从未遇到过的人感到困惑。 我第一次收到此错误消息时,我不知道问题出在哪里(我从未想过不
为什么主体中的这个 static_assert 比我正在检查的错误早于代码中发生的错误?这是标准行为还是特定于编译器? 我很惊讶地发现这是我想要实现的: template Fizzer_t::Fizz
我是一名优秀的程序员,十分优秀!