【问题标题】:Why is C++'s void type only half-heartedly a unit type?为什么 C++ 的 void 类型只是半心半意的单元类型?
【发布时间】:2018-08-16 01:48:15
【问题描述】:

C++ 的void 类型并非无人居住。问题在于,虽然它只有一个居民,很像类似 ML 语言中的 Unit 类型(又名 ()),但该居民不能被命名或作为普通值传递。例如下面的代码编译失败:

void foo(void a) { return; }
void bar() { foo(foo()); }

而等效(比如说)Rust 代码可以编译得很好:

fn foo(a : ()) { return; }
fn bar() { foo(foo(())); }

实际上,void 类似于 unit 类型,但只是半心半意。为什么会这样?

C++ 标准是否明确声明不能创建void 类型的值?如果是,这个决定背后的理由是什么?如果不是,为什么上面的代码不能编译?

如果是向后兼容的原因,请给出代码示例。

需要明确的是,我不是在寻找解决问题的方法(例如,使用空的结构/类)。我想知道现状背后的历史原因。

编辑:我稍微更改了代码示例中的语法,以表明我没有试图劫持现有语法,如void foo(void)(因此,某些 cmets 可能已过时)。问题背后的主要动机是“为什么类型系统不像 X”和 not“为什么这部分语法不像我想要的那样表现”。如果您正在撰写有关破坏向后兼容性的答案,请记住这一点。

【问题讨论】:

  • 有一个regular void 提案,您可能会觉得很有趣。 :)
  • 历史原因是void及其几乎所有属性都直接来自C。问C为什么不从ML中取一个特征是倒退的; C 比 ML 更老。所以我不确定你在问什么;为什么 C++ 没有从 C 的 void 中改变 void?
  • std::monostate 不会是 ML 模拟吗? (而不是解决方法。)或者您是否希望 C++ 更改 void 的语义,并破坏向后兼容性,并显着阻碍 C 互操作性。 (C 互操作性已经有一系列警告、陷阱和关注点。改变 void 的语义将是可怕的。)
  • @theindigamer:另外,说“语句的类型是void”是不正确的。表达式有类型,作为表达式的语句可能没有void 类型,而不是表达式的语句根本没有类型。
  • @theindigamer:仅仅因为一位学者给你一个语言 (Xi) 的描述(在 lambda 演算中),其中语句具有类型,并不意味着它在 C++ 中是正确的。我在该 pdf 中没有看到任何关于 C++ 的声明。

标签: c++ types


【解决方案1】:

“C++ 标准是否明确规定不能创建 void 类型的值?”

是的。它指出void 是一个不完整类型,无法完成。您不能创建类型不完整的对象或值。

这是一条老规矩;正如 cmets 所指出的,它是从 C 继承的。C++ 中有一些小的扩展来简化通用代码的编写,例如void f(); void g() { return f(); } 是合法的。

改变现状似乎收效甚微。 C++ 不是一门学术语言。纯度不是目标。编写有用的程序是有帮助的,但是这样的提议对这有什么帮助呢?引用 Raymond Chen 的话,每个提案都从 -100 开始,并且必须证明其添加是合理的;你没有理由缺乏功能。

【讨论】:

  • 我谨表示不同意。并非所有功能在更改和复杂性(-100 位)方面都具有相同的成本,并且不应遵循相同的标准。我能想到的两个例子——(1)我最近试图写一个 eDSL 并且有一个void 过载将是最符合人体工程学的,(2)添加一个合适的单元类型铺平了道路添加一个适当的无人居住类型,它有自己的set of advantages
  • 另外,w.r.t. “C++ 不是一门学术语言。纯粹不是目标。”。好吧,看看这在语言复杂性方面对我们有何影响?即使解析它也是如此具有挑战性。不消除小的不一致(void 是唯一既不完整又无法完成的类型)使理解类型系统更具挑战性。易于理解也不是目标吗?
  • @theindigamer 我同意你的观点,因为 C++ 已经发展到过于复杂而无法自圆其说。尽管如此,void 的特殊处理似乎是足够的:当您将某事物声明为 void 时,您实际上是在声明它不存在。至少函数参数和返回值是这样的。您提出的是事实上的传递/返回 NULL 。 void* 指针更加特殊,因为它们可以在 C 中隐式转换为任何其他指针类型/从任何其他指针类型转换。C++ 更改了这一点以获得更好的类型安全性,但这就是为什么很多好的 C 代码不是合法的 C++ 代码的主要原因。跨度>
  • @theindigamer:让我更明确一点。 void 是故意不完整的。这为通用库代码的作者提供了一个非常有用的 else-case。如果它不存在,就会有人要求这种类型。你永远无法同时满足这两个要求。同样,有些语言有一个通用的Object 基类,它是独一无二的,因为它本身没有基类。同样,没有客观的衡量标准来判断这是否是一个好的设计..
  • @MSalters 您能否在回答中详细说明“为通用库代码的作者提供了一个非常有用的 else-case”?我知道 Rust 通过在 HashMaps 中将值类型设置为 () 来实现 HashSet(这是一个零成本抽象),但我不知道 C++ 中的类似示例,尽管我很高兴了解它们。如果你想要一个无人居住的类型,Rust 有 Never,Haskell 有 Void
【解决方案2】:

这确实是一个历史问题。旧的(C 之前的)语言用于区分返回值的 函数 和没有返回值的 子例程(哦,Fortran IV 和 Basic 的好老味道......) . AFAIK,早期的 C 只允许函数,简单的函数默认返回 int 并且没有 return 语句是合法的(意味着返回一个未指定的值)并且忽略任何返回值是合法的 - 这样程序员就可以编写连贯的代码。 .. 在那些早期,C 或多或少被用作强大的宏汇编程序,只要编译器可以将其翻译成机器指令(例如没有严格的别名规则......),任何事情都是允许的。由于内存单元是char,所以不需要void *指针,char *就足够了。

然后人们觉得有必要明确缓冲区应该包含任何内容而不是字符串,并且某些函数永远不会返回值。而void 也感受到了差距。

缺点是,当你声明一个void 函数时,你声明了所谓的子例程,它永远不能用作值,特别是永远不能用作一个函数参数。所以void不仅是一种永远无法实例化的特殊类型,它真正声明了结果不能是表达式的成员。

由于语言继承,并且由于 C 标准库仍然是 C++ 标准库的子集,C++ 仍然像 ANSI C 那样处理 void

其他语言可以使用不同的约定。例如,在 Python 中,一个函数总是会返回一些东西,如果没有遇到 return 语句,它只会返回特殊的 None 值。 rust 似乎还有另一个约定。

【讨论】:

  • 只是为了确保我对您的理解正确:您是否(间接地?)建议在类型系统中为void 提供一个居民意味着您必须打破与ANSI C 的向后兼容性?
  • @theindigamer 不。我只是提供历史来解释为什么事情是这样的。
猜你喜欢
  • 1970-01-01
  • 2017-12-03
  • 1970-01-01
  • 2011-05-13
  • 1970-01-01
  • 2013-06-20
  • 2019-03-17
  • 2013-04-26
  • 1970-01-01
相关资源
最近更新 更多