为什么C++中的void方法可以返回void值,但在其他语言中它不能?

Sjo*_*erd 14 c# c++ java void

该程序在C++中编译和运行,但不是Java和C#等多种语言.

#include <iostream>
using namespace std;

void foo2() {
  cout << "foo 2.\n";
}

void foo() {
    return foo2();
}

int main() {

    foo();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

在Java中,这给出了编译器错误,如'Void方法无法返回值'.但由于被调用的方法本身就是一个void,因此它不返回值.我知道为了便于阅读,可能会禁止这样的结构.还有其他异议吗?

编辑:为了将来的参考,我在这里发现了一些类似的问题return-void-type-in​​-c-and-c 我个人认为这个问题还没有回答.回复'因为它在规范中这样说,继续'不会削减它,因为有人必须首先编写规范.也许我应该问'允许返回像C++这样的void类型有什么优缺点'?

The*_*kis 20

这是因为它可以在模板中使用.C#和Java禁止void作为类型参数,但C++允许它允许您编写如下模板代码:

template<typename T, typename TResult>
TResult foo(T x, T y)
{
    return foo2(x, y);
}
Run Code Online (Sandbox Code Playgroud)

如果void不允许方法返回一个void表达式,如果这个模板实例化是不可能的TResultvoid.如果是这种情况,如果您想要TResult实际存在,则需要单独的模板定义void.

例如,请记住在C#中有两组通用通用委托,即Func<>Action<>?嗯,Action<T>正是因为Func<T, void>被禁止而存在.C++设计人员不希望尽可能地引入这样的情况,因此他们决定允许您将其void用作模板参数 - 您发现的案例就是一个促进这一点的功能.


(请允许我以假装 - 问答形式编写其余内容.)

但为什么C#和Java 没有允许类似的结构?

首先,要了解如何在这些语言中实现泛型编程:

为什么选择一种实现通用编程的方法呢?

  • 泛型方法保持了其余语言的名义类型.这样做的好处是允许(AOT)编译器进行静态分析,类型检查,错误报告,重载解析以及最终生成一次代码.
  • 模板方法基本上是鸭子打字.用名义类型语言打字的鸭子没有上述优点,但它允许你更灵活,因为它允许潜在的"无效"事物(从名义类型系统的角度来看"无效")只要你在程序的任何地方都没有提到那些无效的可能性.换句话说,模板允许您统一表达更多的案例.

好的,那么C#和Java需要做些什么才能支持void作为有效的泛型参数呢?

我不得不推测回答这个,但我会试试.

在语言层面,他们必须放弃return;仅在void方法中有效并且对于非void方法始终无效的概念.如果没有这种改变,很少有用的方法可以被实例化 - 并且它们都可能必须以递归或无条件结束throw(满足两种方法void和非void方法而不返回).因此,为了使这个有用,C#和Java还必须引入允许您返回void表达式的C++特性.

好的,我们假设您已经拥有了,现在您可以编写如下代码:

void Foo2() { }
void Foo()
{
    return Foo2();
}
Run Code Online (Sandbox Code Playgroud)

同样,非通用版本在C#和Java中与在C++中一样无用.但让我们继续前进,看看其实际用途,这是在泛型.

您现在应该可以编写这样的通用代码 - TResult现在可以void(除了已经允许的所有其他类型):

TResult Foo<T, TResult>(T a)
{
    return Foo2(a);
}
Run Code Online (Sandbox Code Playgroud)

但要记住,在C#和Java中,重载解析发生在"早期",而不是"迟到".每种可能的重载决策算法都会选择相同的被调用者TResult.并且类型检查器将不得不抱怨,因为您要么void从可能的非void方法返回表达式,要么从可能的方法返回非void表达式void.

换句话说,外部方法不能是通用的,除非:

  1. 被调用者也是通用的,其返回类型由与外部方法匹配的泛型类型参数定义.
  2. 泛型类型和方法中的重载分辨率被推迟,直到实际的类型参数可用,这样我们就可以在调用点选择正确的非泛型方法.

如果我们使用第一个选项怎么办 - 让被调用者的返回类型变为通用并继续前进?

我们可以做到这一点,但它只是将我们的问题推向被调用者.

在某些时候,我们需要一些方法来"实例化"某种void实例,并且可选地能够以某种方式接收它.所以,现在我们需要构造函数void(尽管每一个void方法可以算作一个工厂方法,如果你斜视),我们还需要类型的变量void,可以转换从voidobject,等等.

基本上,void必须成为所有意图和目的的常规类型(例如,常规的空结构).这种含义并不可怕,但我认为你可以看出为什么C#和Java会避免它.

第二个选项怎么样 - 推迟重载决议?

也完全有可能,但请注意,它会有效地将泛型转换为较弱的模板.("较弱"的意思是C++模板不限于类型名称.)

同样,它不会是世界末日,但它将失去我之前描述的泛型的优势.C#和Java的设计者显然希望保留这些优势.


边注:

在C#中,我知道有一个特殊情况,即在验证泛型类型定义之后发生绑定.如果您对a有new()约束T并且您尝试实例化anew T(),则编译器将生成检查是否T为值类型的代码.然后:

这种特殊的情况下,是非常特殊的,因为,即使它已经完全推迟方法绑定到运行时,编译器仍然可以进行静态分析,类型检查和代码生成一次.毕竟,表达式的类型new T()总是如此,T并且可以简单地解析和验证对具有空形式参数列表的事物的调用.