为什么主流的静态类型语言不支持按返回类型重载函数/方法?我想不出有什么能做到。这似乎并不比支持按参数类型重载更有用或更合理。为什么它不那么受欢迎呢?


在这样一种语言中,你将如何解决以下问题:

f(g(x))

如果f有重载void f(int)和void f(字符串)和g有重载int g(int)和字符串g(int)?你需要某种消歧器。

我认为,在需要这个函数的情况下,最好为函数选择一个新名称。


从另一个非常相似的问题(dupe?)中窃取一个c++特定的答案:


函数返回类型不会在重载解析中发挥作用,因为Stroustrup(我假设来自其他c++架构师的输入)希望重载解析是“上下文独立的”。参见“c++编程语言,第三版”中的“重载和返回类型”。

原因是为了保持独立于上下文的单个操作符或函数调用的解析。

They wanted it to be based only on how the overload was called - not how the result was used (if it was used at all). Indeed, many functions are called without using the result or the result would be used as part of a larger expression. One factor that I'm sure came into play when they decided this was that if the return type was part of the resolution there would be many calls to overloaded functions that would need to be resolved with complex rules or would have to have the compiler throw an error that the call was ambiguous.

而且,上帝知道,c++的重载解析已经足够复杂了……


如果函数被返回类型重载并且有这两个重载

int func();
string func();

在看到这样的调用时,编译器无法确定调用这两个函数中的哪一个

void main() 
{
    func();
}

由于这个原因,语言设计者通常不允许返回值重载。

然而,有些语言(如MSIL)允许按返回类型重载。当然,它们也面临上述困难,但它们有变通办法,为此您必须查阅它们的文档。


大多数静态语言现在也支持泛型,这将解决您的问题。如前所述,如果没有参数差异,就无法知道调用哪一个。如果你想这样做,使用泛型就可以了。


与其他人所说的相反,根据返回类型进行重载是可能的,并且一些现代语言已经做到了这一点。通常的反对意见是,在代码中

int func();
string func();
int main() { func(); }

你不能告诉哪个func()被调用。这个问题可以通过以下几种方式解决:

有一个可预测的方法来确定在这种情况下调用哪个函数。 无论何时出现这种情况,都是编译时错误。但是,要有允许程序员消除歧义的语法,例如int main() {(string)func();}。 没有副作用。如果你没有副作用,并且你从不使用函数的返回值,那么编译器可以避免在第一个地方调用函数。

我经常通过返回类型使用重载的两种语言:Perl和Haskell。让我来描述一下他们的工作。

在Perl中,标量上下文和列表上下文(以及其他上下文,但我们假设有两个)之间有基本的区别。Perl中的每个内置函数都可以根据调用它的上下文做不同的事情。例如,连接操作符强制使用列表上下文(在被连接的对象上),而标量操作符强制使用标量上下文,因此比较:

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.

Every operator in Perl does something in scalar context and something in list context, and they may be different, as illustrated. (This isn't just for random operators like localtime. If you use an array @a in list context, it returns the array, while in scalar context, it returns the number of elements. So for example print @a prints out the elements, while print 0+@a prints the size.) Furthermore, every operator can force a context, e.g. addition + forces scalar context. Every entry in man perlfunc documents this. For example, here is part of the entry for glob EXPR:

在列表上下文中,返回一个(可能 空)上的文件名扩展列表 EXPR值如标准 Unix shell /bin/csh可以。在 标量上下文,glob迭代遍历 这样的文件名扩展,返回 当列表耗尽时Undef。

现在,列表和标量上下文之间的关系是什么?嗯,perlfunc说

Remember the following important rule: There is no rule that relates the behavior of an expression in list context to its behavior in scalar context, or vice versa. It might do two totally different things. Each operator and function decides which sort of value it would be most appropriate to return in scalar context. Some operators return the length of the list that would have been returned in list context. Some operators return the first value in the list. Some operators return the last value in the list. Some operators return a count of successful operations. In general, they do what you want, unless you want consistency.

所以这不是一个简单的函数,然后在最后做简单的转换。事实上,出于这个原因,我选择了localtime示例。

并不是只有内置程序才有这种行为。任何用户都可以使用wantarray定义这样的函数,这允许您区分列表、标量和void上下文。例如,你可以决定什么都不做如果你在void context中被调用。

Now, you may complain that this isn't true overloading by return value because you only have one function, which is told the context it's called in and then acts on that information. However, this is clearly equivalent (and analogous to how Perl doesn't allow usual overloading literally, but a function can just examine its arguments). Moreover, it nicely resolves the ambiguous situation mentioned at the beginning of this response. Perl doesn't complain that it doesn't know which method to call; it just calls it. All it has to do is figure out what context the function was called in, which is always possible:

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

This code reads a floating point number from standard input, and prints its square root. But what is surprising about this? Well, the type of readLn is readLn :: Read a => IO a. What this means is that for any type that can be Read (formally, every type that is an instance of the Read type class), readLn can read it. How did Haskell know that I wanted to read a floating point number? Well, the type of sqrt is sqrt :: Floating a => a -> a, which essentially means that sqrt can only accept floating point numbers as inputs, and so Haskell inferred what I wanted.

要是哈斯克尔猜不出我想要什么怎么办?有几种可能性。如果我根本不使用返回值,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: "It might appear that the simplest overload resolution rule is to use everything - all information from as wide a context as possible - to resolve the overloaded reference. This rule may be simple, but it is not helpful. It requires the human reader to scan arbitrarily large pieces of text, and to make arbitrarily complex inferences (such as (g) above). We believe that a better rule is one that makes explicit the task a human reader or a compiler must perform, and that makes this task as natural for the human reader as possible."

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()的调用并确定调用了哪个函数。”(注意,为了便于比较,Haskell中没有隐式转换。)

Java (Java Language Specification 9.4.1): "One of the inherited methods must be return-type-substitutable for every other inherited method, or else a compile-time error occurs." (Yes, I know this doesn't give a rationale. I'm sure the rationale is given by Gosling in "the Java Programming Language". Maybe someone has a copy? I bet it's the "principle of least surprise" in essence.) However, fun fact about Java: the JVM allows overloading by return value! This is used, for example, in Scala, and can be accessed directly through Java as well by playing around with internals.

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
}

在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()

因此没有什么意义。大多数人不感兴趣的是语言是否能帮助你在使用它的时候省去一行字。

模式匹配有点类似于函数重载,我想有时工作方式也类似。但它并不常见,因为它只对少数程序有用,而且在大多数语言上实现起来很棘手。

你可以看到,在语言中还有无限多更好更容易实现的特性,包括:

动态类型 内部支持列表,字典和unicode字符串 优化(JIT、类型推断、编译) 集成部署工具 库支持 社区支持和聚集场所 丰富的标准库 好的语法 读取eval打印循环 对反射编程的支持


好的答案!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++困境的帕累托最优方法:)


如前所述,对仅因返回类型不同而不同的函数的模糊调用会引入模糊。 模糊性会导致有缺陷的代码。 必须避免有缺陷的代码。

试图模糊化所带来的复杂性表明这不是一个好的hack。 除了智力练习之外,为什么不使用带有引用参数的过程呢?

procedure(reference string){};
procedure(reference int){};
string blah;
procedure(blah)

如果你稍微换个角度来看,这个重载特性并不难管理。考虑以下几点:

public Integer | String f(int choice){
if(choice==1){
return new string();
}else{
return new Integer();
}}

如果一种语言确实返回重载,它将允许参数重载,但不允许重复。 这将解决以下问题:

main (){
f(x)
}

因为只有一个f(int)选项可供选择。


在. net中,有时我们使用一个参数来指示一个通用结果的期望输出,然后进行转换以得到我们期望的结果。

C#

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();
    }

也许这个例子也有帮助:

C++

    #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;
}

根据返回元素是标量还是数组,Octave允许不同的结果。

x = min ([1, 3, 0, 2, 0])
   ⇒  x = 0

[x, ix] = min ([1, 3, 0, 2, 0])
   ⇒  x = 0
      ix = 3 (item index)

Cf也是奇异值分解。


这一点在c++中略有不同;我不知道它是否会被认为直接通过返回类型重载。它更像是一种模板专门化,以。

util.h

#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

util.inl

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;    
}

util.cpp

#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*中,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”中的代码??


如果你想重载具有不同返回类型的方法,只需添加一个具有默认值的虚拟参数来允许重载执行,但不要忘记参数类型应该是不同的,因此重载逻辑工作接下来是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;