Answers:
与其他人所说的相反,按返回类型重载是可能的,并且是由某些现代语言完成的。通常的反对意见是在类似
int func();
string func();
int main() { func(); }
您无法分辨func()
正在调用哪个。这可以通过几种方法解决:
int main() { (string)func(); }
。语言我会定期(两个AB:)使用过载返回类型的Perl和哈斯克尔。让我描述一下他们的工作。
在Perl中,标量和列表上下文(以及其他上下文)之间有根本的区别,但我们会假装有两个。Perl中的每个内置函数都可以执行不同的操作,具体取决于调用它的上下文。例如,join
操作员强制列表上下文(在连接的事物上),而scalar
操作员强制标量上下文,因此比较:
print join " ", localtime(); # printed "58 11 2 14 0 109 3 13 0" for me right now
print scalar localtime(); # printed "Wed Jan 14 02:12:44 2009" for me right now.
Perl中的每个运算符都在标量上下文中执行某些操作,而在列表上下文中执行某些操作,如图所示,它们可能有所不同。(这不仅适用于像这样的随机运算符localtime
。如果@a
在列表上下文中使用数组,它将返回该数组,而在标量上下文中,它将返回元素的数量。因此,例如,print @a
打印出元素,而print 0+@a
打印出大小。 )此外,每个运算符都可以强制使用上下文,例如加法+
强制使用标量上下文。man perlfunc
文件中的每个条目都对此进行了记录。例如,这是条目的一部分glob EXPR
:
在列表上下文中,返回值(
EXPR
如标准Unix Shell/bin/csh
会执行的操作)的文件名扩展列表(可能为空)。在标量上下文中,glob遍历此类文件名扩展,当列表用尽时返回undef。
现在,列表和标量上下文之间是什么关系?好吧,man perlfunc
说
请记住以下重要规则:没有规则将表达式在列表上下文中的行为与其在标量上下文中的行为相关联,反之亦然。它可能会做两件完全不同的事情。每个运算符和函数都决定最适合在标量上下文中返回哪种值。一些运算符返回在列表上下文中应返回的列表长度。一些运算符返回列表中的第一个值。一些运算符返回列表中的最后一个值。一些操作员返回成功操作的计数。通常,除非您需要一致性,否则它们会执行您想要的操作。
因此,拥有一个函数并不是一件简单的事,然后最后进行简单的转换。实际上,localtime
由于这个原因,我选择了示例。
具有此行为的不仅仅是内置组件。任何用户都可以使用定义此类函数wantarray
,该函数可让您区分列表,标量和void上下文。因此,例如,如果您在空白上下文中被调用,则可以决定不执行任何操作。
现在,您可能会抱怨返回值不是真正的重载,因为您只有一个函数,该函数会在调用上下文时被告知,然后根据该信息进行操作。但是,这显然是等效的(类似于Perl不允许字面上通常进行的重载,但是函数只能检查其参数)。此外,它很好地解决了此响应开始时提到的模棱两可的情况。Perl不会抱怨它不知道调用哪种方法。它只是称之为。它要做的就是找出调用该函数的上下文,这始终是可能的:
sub func {
if( not defined wantarray ) {
print "void\n";
} elsif( wantarray ) {
print "list\n";
} else {
print "scalar\n";
}
}
func(); # prints "void"
() = func(); # prints "list"
0+func(); # prints "scalar"
(注意:当我指的是函数时,我有时可能会说Perl运算符。这对本次讨论并不重要。)
Haskell采用另一种方法,即没有副作用。它还具有强大的类型系统,因此您可以编写如下代码:
main = do n <- readLn
print (sqrt n) -- note that this is aligned below the n, if you care to run this
此代码从标准输入读取浮点数,并打印其平方根。但是,对此有何惊奇?好吧,类型readLn
是readLn :: Read a => IO a
。这意味着对于任何可能的类型Read
(形式上是Read
类型类实例的每个类型),readLn
都可以读取它。Haskell如何知道我想读取浮点数?好的,类型sqrt
是sqrt :: Floating a => a -> a
,这实际上意味着sqrt
只能接受浮点数作为输入,因此Haskell推断了我想要的内容。
Haskell无法推断我想要什么会发生什么?好吧,有几种可能性。如果我根本不使用返回值,Haskell根本不会首先调用该函数。但是,如果我确实使用了返回值,那么Haskell将会抱怨它无法推断类型:
main = do n <- readLn
print n
-- this program results in a compile-time error "Unresolved top-level overloading"
我可以通过指定所需的类型来解决歧义:
main = do n <- readLn
print (n::Int)
-- this compiles (and does what I want)
无论如何,整个讨论意味着通过返回值进行重载是可能的并且已经完成,这回答了您的部分问题。
问题的另一部分是为什么更多的语言不这样做。我让别人回答。但是,有几点评论:原则上的原因可能是,与参数类型的重载相比,混淆的机会确实更大。您还可以查看各种语言的基本原理:
Ada:“最简单的重载解决方案规则似乎是使用一切-尽可能从尽可能广泛的上下文中获取所有信息-来解析重载引用。此规则可能很简单,但无济于事。它需要人类阅读者扫描任意大块的文本并做出任意复杂的推断(例如上面的(g))。我们认为,更好的规则是明确规定人类阅读器或编译器必须执行的任务,并使该任务对人类读者来说,这是自然而然的事情。”
C ++(Bjarne Stroustrup的“ C ++编程语言”的7.4.1小节):“重载解析中不考虑返回类型。原因是保持解析度独立于单个运算符或函数调用,与上下文无关。请考虑:
float sqrt(float);
double sqrt(double);
void f(double da, float fla)
{
float fl = sqrt(da); // call sqrt(double)
double d = sqrt(da); // call sqrt(double)
fl = sqrt(fla); // call sqrt(float)
d = sqrt(fla); // call sqrt(float)
}
如果考虑到返回类型,将不再可能单独查看sqrt()
in 的调用并确定调用了哪个函数。”(请注意,为了进行比较,在Haskell中没有隐式转换。)
Java(Java语言规范9.4.1):“其中一个继承的方法必须可以对其他所有继承的方法进行返回类型替换,否则将发生编译时错误。” (是的,我知道这并没有给出基本原理。我确定基本原理是Gosling在“ Java编程语言”中给出的。也许有人抄袭了吗?我敢打赌,这实质上是“最不令人惊讶的原则”。 )但是,关于Java的一个有趣的事实是:JVM 允许通过返回值进行重载!例如,它在Scala中使用,并且可以通过内部玩法直接通过Java访问。
PS。最后一点,实际上可以通过技巧在C ++中通过返回值重载。证人:
struct func {
operator string() { return "1";}
operator int() { return 2; }
};
int main( ) {
int x = func(); // calls int version
string y = func(); // calls string version
double d = func(); // calls int version
cout << func() << endl; // calls int version
func(); // calls neither
}
Foo
并Bar
支持双向转换,并且一种方法在Foo
内部使用type 但返回type Bar
。如果代码调用了这样的方法,该方法将立即将结果强制为type Foo
,则可以使用Bar
return类型,但是Foo
这样做会更好。顺便说一句,我也想看看......
var someVar = someMethod();
(或者指定不应以这种方式使用其返回值)。例如,实现Fluent接口的一类类型可能会受益于具有可变和不可变的版本,因此var thing2 = thing1.WithX(3).WithY(5).WithZ(9);
将WithX(3)
复制thing1
到可变对象,对X进行突变并返回该可变对象。WithY(5)
会使Y变异并返回相同的对象;同样是WithZ(9)。然后,分配将转换为不可变的类型。
如果函数由返回类型重载,而您有这两个重载
int func();
string func();
看到这样的调用,编译器无法找出要调用的两个函数中的哪个
void main()
{
func();
}
因此,语言设计人员通常不允许返回值重载。
但是,某些语言(例如MSIL)确实允许按返回类型进行重载。他们当然也面临上述困难,但是他们有变通办法,您必须查阅其文档。
用这种语言,您将如何解决以下问题:
f(g(x))
如果f
有过载void f(int)
,void f(string)
并且g
有过载int g(int)
和string g(int)
?您将需要某种歧义消除器。
我认为通过为函数选择一个新名称可以更好地满足您可能需要的情况。
要从另一个非常相似的问题(重复?)窃取C ++特定的答案:
函数返回类型不会在重载解析中起作用,仅仅是因为Stroustrup(我假设其他C ++架构师的输入)希望重载解析是“上下文无关的”。请参见“ C ++编程语言,第三版”中的7.4.1-“重载和返回类型”。
原因是保持解析度独立于单个运算符或函数调用。
他们希望它仅基于重载的调用方式,而不是基于结果的使用方式(如果完全使用了结果)。实际上,在不使用结果的情况下调用了许多函数,或者将结果用作较大表达式的一部分。我确定他们决定这样做的一个因素是,如果返回类型是解决方案的一部分,那么将会有很多对重载函数的调用,这些调用需要使用复杂的规则来解决,或者必须让编译器抛出通话不明确的错误。
而且,Lord知道,C ++重载解析足够复杂了……
在haskell中,即使它没有函数重载也是可能的。Haskell使用类型类。在程序中,您可以看到:
class Example a where
example :: Integer -> a
instance Example Integer where -- example is now implemented for Integer
example :: Integer -> Integer
example i = i * 10
函数重载本身并不那么流行。我使用过的大多数语言都是C ++,也许是Java和/或C#。在所有动态语言中,它是以下各项的简写:
define example:i
↑i type route:
Integer = [↑i & 0xff]
String = [↑i upper]
def example(i):
if isinstance(i, int):
return i & 0xff
elif isinstance(i, str):
return i.upper()
因此,它没有太多意义。大多数人都不关心语言是否可以帮助您在使用的任何位置放一行。
模式匹配在某种程度上类似于函数重载,而且我猜有时有时也是如此。但这并不常见,因为它仅对少数程序有用,并且很难在大多数语言上实现。
您会看到有很多其他更易于实现的功能可以实现到语言中,包括:
好答案!A.Rex的答案特别详细和有启发性。正如他指出的那样,C ++ 在编译时确实考虑了用户提供的类型转换运算符lhs = func();
(其中func实际上是结构的名称)。我的解决方法有所不同-更好,只是有所不同(尽管它基于相同的基本思想)。
而我想要写的...
template <typename T> inline T func() { abort(); return T(); }
template <> inline int func()
{ <<special code for int>> }
template <> inline double func()
{ <<special code for double>> }
.. etc, then ..
int x = func(); // ambiguous!
int x = func<int>(); // *also* ambiguous!? you're just being difficult, g++!
我最终得到了一个使用参数化结构的解决方案(T =返回类型):
template <typename T>
struct func
{
operator T()
{ abort(); return T(); }
};
// explicit specializations for supported types
// (any code that includes this header can add more!)
template <> inline
func<int>::operator int()
{ <<special code for int>> }
template <> inline
func<double>::operator double()
{ <<special code for double>> }
.. etc, then ..
int x = func<int>(); // this is OK!
double d = func<double>(); // also OK :)
该解决方案的好处是,包含这些模板定义的任何代码都可以为更多类型添加更多专业化特性。您也可以根据需要对结构进行部分专业化。例如,如果要对指针类型进行特殊处理:
template <typename T>
struct func<T*>
{
operator T*()
{ <<special handling for T*>> }
};
否定的是,您无法int x = func();
使用我的解决方案进行写作。你必须写int x = func<int>();
。您必须明确地说出返回类型是什么,而不是通过查看类型转换运算符来要求编译器暂停它。我会说“我的”解决方案和A.Rex都属于解决这种C ++难题的方式的最优途径:)
如果您想重载具有不同返回类型的方法,只需添加一个带有默认值的虚拟参数以允许重载执行,但不要忘记参数类型应该有所不同,因此重载逻辑的下一个工作原理例如在delphi上:
type
myclass = class
public
function Funct1(dummy: string = EmptyStr): String; overload;
function Funct1(dummy: Integer = -1): Integer; overload;
end;
这样使用
procedure tester;
var yourobject : myclass;
iValue: integer;
sValue: string;
begin
yourobject:= myclass.create;
iValue:= yourobject.Funct1(); //this will call the func with integer result
sValue:= yourobject.Funct1(); //this will call the func with string result
end;
如前所述,仅返回类型不同的函数的歧义调用会引起歧义。歧义导致缺陷代码。必须避免有缺陷的代码。
模棱两可的尝试导致的复杂性表明,这不是一个好办法。除了进行智力练习外,为什么不使用带有参考参数的程序。
procedure(reference string){};
procedure(reference int){};
string blah;
procedure(blah)
doing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
在.NET中,有时我们使用一个参数来指示通用结果的所需输出,然后进行转换以获取期望的结果。
public enum FooReturnType{
IntType,
StringType,
WeaType
}
class Wea {
public override string ToString()
{
return "Wea class";
}
}
public static object Foo(FooReturnType type){
object result = null;
if (type == FooReturnType.IntType)
{
/*Int related actions*/
result = 1;
}
else if (type == FooReturnType.StringType)
{
/*String related actions*/
result = "Some important text";
}
else if (type == FooReturnType.WeaType)
{
/*Wea related actions*/
result = new Wea();
}
return result;
}
static void Main(string[] args)
{
Console.WriteLine("Expecting Int from Foo: " + Foo(FooReturnType.IntType));
Console.WriteLine("Expecting String from Foo: " + Foo(FooReturnType.StringType));
Console.WriteLine("Expecting Wea from Foo: " + Foo(FooReturnType.WeaType));
Console.Read();
}
也许这个例子也有帮助:
#include <iostream>
enum class FooReturnType{ //Only C++11
IntType,
StringType,
WeaType
}_FooReturnType;
class Wea{
public:
const char* ToString(){
return "Wea class";
}
};
void* Foo(FooReturnType type){
void* result = 0;
if (type == FooReturnType::IntType) //Only C++11
{
/*Int related actions*/
result = (void*)1;
}
else if (type == FooReturnType::StringType) //Only C++11
{
/*String related actions*/
result = (void*)"Some important text";
}
else if (type == FooReturnType::WeaType) //Only C++11
{
/*Wea related actions*/
result = (void*)new Wea();
}
return result;
}
int main(int argc, char* argv[])
{
int intReturn = (int)Foo(FooReturnType::IntType);
const char* stringReturn = (const char*)Foo(FooReturnType::StringType);
Wea *someWea = static_cast<Wea*>(Foo(FooReturnType::WeaType));
std::cout << "Expecting Int from Foo: " << intReturn << std::endl;
std::cout << "Expecting String from Foo: " << stringReturn << std::endl;
std::cout << "Expecting Wea from Foo: " << someWea->ToString() << std::endl;
delete someWea; // Don't leak oil!
return 0;
}
对于C ++,这一点略有不同。我不知道返回类型是否会将其直接视为过载。更多的是模板专门化以这种方式起作用。
实用程序
#ifndef UTIL_H
#define UTIL_H
#include <string>
#include <sstream>
#include <algorithm>
class util {
public:
static int convertToInt( const std::string& str );
static unsigned convertToUnsigned( const std::string& str );
static float convertToFloat( const std::string& str );
static double convertToDouble( const std::string& str );
private:
util();
util( const util& c );
util& operator=( const util& c );
template<typename T>
static bool stringToValue( const std::string& str, T* pVal, unsigned numValues );
template<typename T>
static T getValue( const std::string& str, std::size_t& remainder );
};
#include "util.inl"
#endif UTIL_H
实用程序
template<typename T>
static bool util::stringToValue( const std::string& str, T* pValue, unsigned numValues ) {
int numCommas = std::count(str.begin(), str.end(), ',');
if (numCommas != numValues - 1) {
return false;
}
std::size_t remainder;
pValue[0] = getValue<T>(str, remainder);
if (numValues == 1) {
if (str.size() != remainder) {
return false;
}
}
else {
std::size_t offset = remainder;
if (str.at(offset) != ',') {
return false;
}
unsigned lastIdx = numValues - 1;
for (unsigned u = 1; u < numValues; ++u) {
pValue[u] = getValue<T>(str.substr(++offset), remainder);
offset += remainder;
if ((u < lastIdx && str.at(offset) != ',') ||
(u == lastIdx && offset != str.size()))
{
return false;
}
}
}
return true;
}
实用程序
#include "util.h"
template<>
int util::getValue( const std::string& str, std::size_t& remainder ) {
return std::stoi( str, &remainder );
}
template<>
unsigned util::getValue( const std::string& str, std::size_t& remainder ) {
return std::stoul( str, &remainder );
}
template<>
float util::getValue( const std::string& str, std::size_t& remainder ) {
return std::stof( str, &remainder );
}
template<>
double util::getValue( const std::string& str, std::size_t& remainder ) {
return std::stod( str, &remainder );
}
int util::convertToInt( const std::string& str ) {
int i = 0;
if ( !stringToValue( str, &i, 1 ) ) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to int";
throw strStream.str();
}
return i;
}
unsigned util::convertToUnsigned( const std::string& str ) {
unsigned u = 0;
if ( !stringToValue( str, &u, 1 ) ) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to unsigned";
throw strStream.str();
}
return u;
}
float util::convertToFloat(const std::string& str) {
float f = 0;
if (!stringToValue(str, &f, 1)) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to float";
throw strStream.str();
}
return f;
}
double util::convertToDouble(const std::string& str) {
float d = 0;
if (!stringToValue(str, &d, 1)) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " Bad conversion of [" << str << "] to double";
throw strStream.str();
}
return d;
}
此示例未完全按返回类型使用函数重载解析,但是此c ++非对象类使用模板专用化通过私有静态方法按返回类型模拟函数重载解析。
每个convertToType
函数都在调用函数模板stringToValue()
,如果您查看该函数模板的实现细节或算法,它将调用该函数getValue<T>( param, param )
并返回一个类型T
并将其存储到一个作为参数之一T*
传递给stringToValue()
函数模板的类型中。
除了这样的事情 C ++实际上并没有一种机制可以通过返回类型来实现函数重载解析。我可能不知道还有其他构造或机制可以按返回类型模拟解析。
我认为这是现代C ++定义中的GAP ...为什么?
int func();
double func();
// example 1. → defined
int i = func();
// example 2. → defined
double d = func();
// example 3. → NOT defined. error
void main()
{
func();
}
为什么C ++编译器不能在示例“ 3”中引发错误并不能在示例“ 1 + 2”中接受代码?
现在,大多数静态语言还支持泛型,这将解决您的问题。如前所述,没有参数diffs,就无法知道要调用哪个参数。因此,如果要执行此操作,只需使用泛型并将其命名为一天。