设为首页 加入收藏

TOP

为什么你的static_assert不能按预期的工作?(一)
2023-07-23 13:34:20 】 浏览:61
Tags:static_assert 能按预

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:
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇<一>函数模板 下一篇关于scanf函数不能在某些vs编译器..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目