这是因为它可以在模板中使用。C# 和 Java 禁止作为类型参数,但C++允许它允许您编写如下模板代码:void
template<typename T, typename TResult>
TResult foo(T x, T y)
{
return foo2(x, y);
}
如果不允许方法返回表达式,则 如果 是 ,则无法进行此模板实例化。如果是这种情况,如果您想要实际使用,则需要一个单独的模板定义。void
void
TResult
void
TResult
void
例如,请记住在 C# 中如何有两组通用通用委托,即 和 ?好吧,存在正是因为被禁止。C++设计师不想尽可能地引入这样的情况,所以他们决定允许你用作模板参数 - 你发现的情况是一个功能,可以促进这一点。Func<>
Action<>
Action<T>
Func<T, void>
void
(请允许我以假装问答的形式编写其余部分。
但是为什么C#和Java不允许类似的结构呢?
首先,了解如何在这些语言中实现泛型编程:
为什么选择一种实现泛型编程的方法而不是另一种方法?
- 泛型方法保持语言其余部分的名义类型。这样做的好处是允许(AOT)编译器进行静态分析,类型检查,错误报告,重载解决以及最终一次代码生成。
- 模板方法本质上是鸭子类型。Duck在名义上类型化的语言中打字没有上述优点,但它可以让你有更大的灵活性,因为它将允许潜在的“无效”的东西(从名义类型系统的角度来看是“无效的”),只要你没有在程序的任何地方提到这些无效的可能性。换句话说,模板允许您统一表达更大的案例集。
好吧,那么C#和Java需要做些什么来支持作为有效的泛型参数呢?void
我必须推测才能回答这个问题,但我会尝试。
在语言层面,他们必须放弃仅在方法中有效且对非方法始终无效的概念。如果没有此更改,很少有有用的方法可以实例化 - 并且它们可能都必须以递归或无条件(满足void
和非void
方法而不返回)结束。因此,为了使这很有用,C#和Java还必须引入C++功能,允许您返回表达式。return;
void
void
throw
void
好吧,让我们假设你有这个,现在你可以写这样的代码:
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
换句话说,外部方法不能是泛型的,除非:
- 被调用方也是泛型的,其返回类型由与外部方法的泛型类型参数匹配的泛型类型参数定义。
- 泛型类型和方法中的重载解析被推迟到实际的类型参数可用时,以便我们可以在调用点选择正确的非泛型方法。
如果我们选择第一个选项 - 使被调用方的返回类型通用并继续前进呢?
我们可以这样做,但它只是把我们的问题推给被叫方。
在某些时候,我们需要某种方法来“实例化”某种实例,并可以选择以某种方式接收它。所以现在我们需要构造函数(尽管每个方法都可以算作一个工厂方法,如果你眯眼的话),我们还需要类型的变量,可能的从到的转换,等等。void
void
void
void
void
object
基本上,对于所有意图和目的,都必须成为常规类型(例如,常规空结构)。这其中的含义并不可怕,但我认为你可以理解为什么C#和Java避免了它。void
第二种选择 - 推迟过载解决方案呢?
也是完全可能的,但请注意,它将有效地将泛型转换为较弱的模板。(“较弱”的意思是,C++模板不限于类型名称。
同样,这不会是世界末日,但它将涉及失去我之前描述的泛型的优势。C#和Java的设计者显然希望保留这些优势。
附注:
在 C# 中,我知道有一种特殊情况,即绑定发生在泛型类型定义验证之后。如果 对 有约束,并且尝试实例化新的 T(),
编译器将生成代码来检查是否为值类型。然后:new()
T
T
这种特殊情况非常特殊,因为即使它完全推迟了对运行时的方法绑定,编译器仍然可以执行静态分析、类型检查和代码生成一次。毕竟,表达式的类型总是这样,对具有空形式参数列表的内容的调用可以简单地解析和验证。new T()
T