底类型
在范畴论中经常会谈起这种特殊的类型—— 0 类型。在 TypeScript 中它被称为 never
,在 Rust 中被称为 !
,在 Haskell 中则被称为 Void
。它并不是 C++ 的 void
类型——虽然看上去很像;它是数学上的空集 ,映射到编程语言中的一种类型。0 类型刚好是顶类型的反义词,因此也被称作底类型(Bottom type),正如标题所示。
之前介绍 C++ 的 void
关键字时,提到过它引入的类型叫做“空类型”,而且不存在空类型的变量。但是如果把“返回 void
的函数”的数学定义写出来,就会发现一些问题。
如果一个数学上的函数的值域是空集,其实意味着这个函数根本不会求值完成。换到编程语言的视角,就是“只有不可能返回的函数,才可以将返回值类型定义为‘空集’”。
而 C++ 中的 void
,则更像是一个特殊的、有且只有一个唯一值的类型。我们姑且按照 JavaScript 的记法,管它叫做 undefined
。任何一个返回值类型为 void
的函数,它的 return 语句和函数体结尾都相当于一句 return undefined
。这样,函数的调用者接收到了这个 undefined
值,程序才能继续运行下去。虽然,undefined
值并不存在——C++ 不允许 void
类型的值出现。
回到 0 类型的话题。如果一个函数返回 0 类型,则意味着它不会返回。如果它返回了,就意味着被调用方接受了一个 0 类型的值,这就与 0 类型的定义矛盾了。那么,一个函数什么时候可以不返回呢?
在 C++ 中,答案有这样几种:
- 无限循环(不停机)。
- 程序终止。
- 抛出异常。
- 调用了其它返回 0 类型的函数。
不论哪一种情形,都会导致这个函数调用之后的后续代码绝对不会被执行。在 C++ 中,如果一个函数不会返回,则你可以将它标记为 [[noreturn]]
:
[[noreturn]]
void f() {
while (true);
}
[[noreturn]]
void g() {
std::exit();
}
[[noreturn]]
void h() {
throw std::runtime_error("");
}
在 C++ 中,两个左方括号 [[
代表这里要插入一个 C++ 特性(Attribute)。特性可以插入在几乎代码的任何地方,代表修饰某个东西——比如这里的 [[noreturn]]
改变了函数(数学意义上)的返回值类型;从 1 类型的 void
变成了 0 类型。
虽然 0 类型在数学上有很特殊的地位,但 [[noreturn]]
与否对编译器的编译结果没有太多影响,C++ 编译器也没有聪明到对 [[noreturn]]
做类型检查。不过,如果 [[noreturn]]
可以提示编译器这里做更多优化,比如最激进的,可以直接删除对应函数调用后的所有代码。
想必也不用我多说了:从
[[noreturn]]
函数返回导致未定义行为。