java - 为什么 C++ 中的 void 方法可以返回空值,而在其他语言中却不能?

标签 java c# c++ void

该程序使用 C++ 编译和运行,但不适用于许多不同的语言,如 Java 和 C#。

#include <iostream>
using namespace std;

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

void foo() {
    return foo2();
}

int main() {

    foo();

    return 0;
}

在 Java 中,这会导致编译器错误,例如“Void 方法无法返回值”。但是由于被调用的方法本身就是一个空值,它不返回值。我知道为了可读性可能会禁止这样的构造。还有其他反对意见吗?

编辑:为了将来引用,我在这里发现了一些类似的问题 return-void-type-in-c-and-c
在我看来,这个问题还没有得到解答。
回答“因为它在规范中这么说,继续前进”并没有削减它,因为首先必须有人编写规范。也许我应该问“允许返回像 C++ 这样的 void 类型的利弊是什么”?

最佳答案

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

template<typename T, typename TResult>
TResult foo(T x, T y)
{
    return foo2(x, y);
}

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

例如,请记住在 C# 中有两组通用的通用委托(delegate),即 Func<>Action<> ?那么,Action<T>存在正是因为Func<T, void>是禁止的。 C++ 设计者不想尽可能引入这样的情况,因此他们决定允许您使用 void作为模板参数——您发现的案例正是促进这一点的功能。

(请允许我以假装问答的形式写下其余部分。)

But why do C# and Java not allow a similar construct?



首先,了解如何在这些语言中实现泛型编程:
  • C# 和 Java 泛型通过解析泛型类型(或方法)定义并确保它对您提供的泛型约束/边界有效来工作。
  • C++ 模板是 a search-and-replace mechanism周围有强大的元编程语言。 They are not required to make sense in the absence of specific template arguments ——只有当他们接触到实际参数时,他们才会从“模板元语言”转向“C++语言”(可以这么说)。

  • Why pick one approach of implementing generic programming over the other?


  • 泛型方法维护了 nominal typing其余的语言。这具有允许 (AOT) 编译器进行静态分析、类型检查、错误报告、重载解析和最终代码生成一次的优点。
  • 模板方法本质上是 duck typing .以名义类型语言进行的 Duck 类型没有上述优点,但它允许您具有更大的灵活性,因为它允许潜在的“无效”事物(从名义类型系统的角度来看“无效”)作为只要您实际上没有在程序中的任何地方提及那些无效的可能性。换句话说,模板允许您统一表达更大的案例集。

  • Okay, so what would C# and Java need to do to support void as a valid generic argument?



    我不得不推测来回答这个问题,但我会尝试。

    在语言层面,他们将不得不放弃 return; 的概念。仅在 void 中有效方法并且对于非 void 总是无效的方法。如果没有这个改变,很少有有用的方法可以被实例化——而且它们可能都必须以递归或无条件 throw 结束。 ( which satisfies both void and non- void methods without returning )。因此,要使其有用,C# 和 Java 还必须引入允许您返回 void 的 C++ 特性。表达式。

    好的,让我们假设你有这个,现在你可以写这样的代码:
    void Foo2() { }
    void Foo()
    {
        return Foo2();
    }
    

    同样,非泛型版本在 C# 和 Java 中与在 C++ 中一样无用。但是让我们继续看它真正的用处,那就是泛型。

    您现在应该能够编写这样的通用代码 -- 和 TResult现在可以是 void (除了已经允许的所有其他类型):
    TResult Foo<T, TResult>(T a)
    {
        return Foo2(a);
    }
    

    但请记住,在 C# 和 Java 中,重载解析发生“早”,而不是“晚”。重载解析算法将为每个可能的 TResult 选择相同的被调用者.并且类型检查器将不得不提示,因为您要么返回 void来自可能非 void 的表达式方法或者你返回一个非 void来自可能 void 的表达式方法。

    换句话说,外部方法不能是通用的,除非:
  • 被调用者也是泛型的,其返回类型由与外部方法的类型参数匹配的泛型类型参数定义。
  • 泛型类型和方法中的重载解析被推迟到实际类型参数可用时,以便我们可以在调用点选择正确的非泛型方法。

  • What if we went with the first option - make the callee's return type generic and move on?



    我们可以这样做,但它只是将我们的问题推给了被调用者。

    在某些时候,我们需要某种方式来“实例化”某种 void实例并可选择以某种方式接收它。所以现在我们需要 void 的构造函数(尽管每个 void 方法都可以算作工厂方法,如果你眯着眼睛看的话)而且我们还需要 void 类型的变量,可能来自 void 的转换至 object , 等等。

    基本上,void对于所有意图和目的,都必须成为常规类型(例如 a regular empty struct )。这的含义并不可怕,但我认为您可以理解为什么 C# 和 Java 避免了它。

    What about the second option - postpone overload resolution?



    也完全有可能,但请注意,它会有效地将泛型转换为较弱的模板。 (“较弱”的意思是 C++ templates aren't restricted to typenames 。)

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

    边注:

    在 C# 中,我知道有一种特殊情况,即在验证泛型类型定义之后进行绑定(bind)。如果您有 new()T 的约束而你试图instantiate a new T() ,编译器将生成检查是否 T 的代码是不是值类型。然后:
  • 对于值类型,new T()变成 default(T) -- 记得C# default struct constructors aren't really constructors in the CLR sense .
  • 对于引用类型,Activator.CreateInstance被调用,这是使用反射的间接构造函数调用。

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

    关于java - 为什么 C++ 中的 void 方法可以返回空值,而在其他语言中却不能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36404736/

    相关文章:

    java - 将值放入 HashMap 后更改值会更改 HashMap 中的内容?

    java - 星级显示不正确

    c# - 将文本框值复制到另一个 - ASP.NET MVC Razor

    c# - "System.Math.Cos"可以返回一个(float)吗?

    C++:追加到 vector 字符串

    java - 在列表项上滑动 Action ?

    java - getter 返回 null

    c# - TableAdapter 向导没有来自选择列表中设置的连接字符串

    c++ - 从 lambda 函数构造的 boost::function_output_iterator 不可赋值

    c++ - 在构造函数和类中调用的枚举