底类型

在范畴论中经常会谈起这种特殊的类型—— 0 类型。在 TypeScript 中它被称为 never,在 Rust 中被称为 !,在 Haskell 中则被称为 Void。它并不是 C++ 的 void 类型——虽然看上去很像;它是数学上的空集 \varnothing,映射到编程语言中的一种类型。0 类型刚好是顶类型的反义词,因此也被称作底类型(Bottom type),正如标题所示。

之前介绍 C++ 的 void 关键字时,提到过它引入的类型叫做“空类型”,而且不存在空类型的变量。但是如果把“返回 void 的函数”的数学定义写出来,就会发现一些问题。

如果一个数学上的函数的值域是空集,其实意味着这个函数根本不会求值完成。换到编程语言的视角,就是“只有不可能返回的函数,才可以将返回值类型定义为‘空集’”。

而 C++ 中的 void,则更像是一个特殊的、有且只有一个唯一值的类型。我们姑且按照 JavaScript 的记法,管它叫做 undefined。任何一个返回值类型为 void 的函数,它的 return 语句和函数体结尾都相当于一句 return undefined。这样,函数的调用者接收到了这个 undefined 值,程序才能继续运行下去。虽然,undefined 值并不存在——C++ 不允许 void 类型的值出现。

回到 0 类型的话题。如果一个函数返回 0 类型,则意味着它不会返回。如果它返回了,就意味着被调用方接受了一个 0 类型的值,这就与 0 类型的定义矛盾了。那么,一个函数什么时候可以不返回呢?

在 C++ 中,答案有这样几种:

  1. 无限循环(不停机)。
  2. 程序终止。
  3. 抛出异常。
  4. 调用了其它返回 0 类型的函数。

不论哪一种情形,都会导致这个函数调用之后的后续代码绝对不会被执行。在 C++ 中,如果一个函数不会返回,则你可以将它标记为 [[noreturn]]

在 C++ 中,两个左方括号 [[ 代表这里要插入一个 C++ 特性(Attribute)。特性可以插入在几乎代码的任何地方,代表修饰某个东西——比如这里的 [[noreturn]] 改变了函数(数学意义上)的返回值类型;从 1 类型的 void 变成了 0 类型。

虽然 0 类型在数学上有很特殊的地位,但 [[noreturn]] 与否对编译器的编译结果没有太多影响,C++ 编译器也没有聪明到对 [[noreturn]] 做类型检查。不过,如果 [[noreturn]] 可以提示编译器这里做更多优化,比如最激进的,可以直接删除对应函数调用后的所有代码。

想必也不用我多说了:从 [[noreturn]] 函数返回导致未定义行为。

最近更新:
代码未运行