Funktionsüberladung nach Rückgabetyp?


252

Warum unterstützen nicht mehr statisch typisierte Mainstream-Sprachen das Überladen von Funktionen / Methoden nach Rückgabetyp? Mir fällt nichts ein. Es scheint nicht weniger nützlich oder vernünftig zu sein, als die Überladung nach Parametertyp zu unterstützen. Wie kommt es, dass es so viel weniger beliebt ist?


Antworten:


523

Im Gegensatz zu dem, was andere sagen, Überlastung durch Rückgabetyp ist möglich und wird von einigen modernen Sprachen gemacht. Der übliche Einwand ist, dass in Code wie

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

Sie können nicht sagen, welche func()aufgerufen wird. Dies kann auf verschiedene Arten gelöst werden:

  1. Haben Sie eine vorhersehbare Methode, um zu bestimmen, welche Funktion in einer solchen Situation aufgerufen wird.
  2. Immer wenn eine solche Situation auftritt, handelt es sich um einen Fehler bei der Kompilierung. Haben Sie jedoch eine Syntax, die es dem Programmierer ermöglicht, zu unterscheiden, z int main() { (string)func(); }.
  3. Keine Nebenwirkungen haben. Wenn Sie keine Nebenwirkungen haben und niemals den Rückgabewert einer Funktion verwenden, kann der Compiler vermeiden, die Funktion überhaupt aufzurufen.

Zwei der Sprachen, in denen ich regelmäßig ( ab ) Überladung nach Rückgabetyp verwende: Perl und Haskell . Lassen Sie mich beschreiben, was sie tun.

In Perl gibt es einen grundlegenden Unterschied zwischen skalaren und Liste Kontext (und anderen, aber wir werden es zwei vorgeben ist). Jede in Perl integrierte Funktion kann je nach Kontext unterschiedliche Funktionen ausführen . Beispielsweise joinerzwingt der Operator den Listenkontext (für das zu verbindende Objekt), während der scalarOperator den skalaren Kontext erzwingt. Vergleichen Sie also:

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.

Jeder Operator in Perl führt etwas im skalaren Kontext und etwas im Listenkontext aus, und sie können, wie dargestellt, unterschiedlich sein. (Dies gilt nicht nur für zufällige Operatoren wie localtime. Wenn Sie ein Array @aim Listenkontext verwenden, wird das Array zurückgegeben, während im skalaren Kontext die Anzahl der Elemente zurückgegeben wird. So werden beispielsweise print @adie Elemente ausgedruckt, während print 0+@adie Größe gedruckt wird. ) Darüber hinaus kann jeder Operator einen Kontext erzwingen , z. B. Addition +erzwingt skalaren Kontext. Jeder Eintrag in man perlfuncdokumentiert dies. Hier ist zum Beispiel ein Teil des Eintrags für glob EXPR:

Gibt im Listenkontext eine (möglicherweise leere) Liste von Dateinamenerweiterungen für den Wert von zurück, EXPRwie dies die Standard-Unix-Shell /bin/cshtun würde. Im skalaren Kontext durchläuft glob solche Dateinamenerweiterungen und gibt undef zurück, wenn die Liste erschöpft ist.

Wie ist nun die Beziehung zwischen Liste und skalarem Kontext? Nun, man perlfuncsagt

Beachten Sie die folgende wichtige Regel: Es gibt keine Regel, die das Verhalten eines Ausdrucks im Listenkontext mit seinem Verhalten im skalaren Kontext in Beziehung setzt oder umgekehrt. Es könnte zwei völlig verschiedene Dinge tun. Jeder Operator und jede Funktion entscheidet, welche Art von Wert im skalaren Kontext am besten zurückgegeben werden soll. Einige Operatoren geben die Länge der Liste zurück, die im Listenkontext zurückgegeben worden wäre. Einige Operatoren geben den ersten Wert in der Liste zurück. Einige Operatoren geben den letzten Wert in der Liste zurück. Einige Bediener geben eine Anzahl erfolgreicher Vorgänge zurück. Im Allgemeinen tun sie, was Sie wollen, es sei denn, Sie wollen Konsistenz.

Es ist also nicht einfach, eine einzige Funktion zu haben, und am Ende führen Sie eine einfache Konvertierung durch. Tatsächlich habe ich das localtimeBeispiel aus diesem Grund gewählt.

Es sind nicht nur die integrierten Funktionen, die dieses Verhalten aufweisen. Jeder Benutzer kann eine solche Funktion mit definieren wantarray, mit der Sie zwischen Listen-, Skalar- und Leerkontext unterscheiden können. So können Sie beispielsweise entscheiden, nichts zu tun, wenn Sie im ungültigen Kontext aufgerufen werden.

Jetzt können Sie sich beschweren, dass dies keine echte Überladung durch den Rückgabewert ist, da Sie nur eine Funktion haben, der der Kontext mitgeteilt wird, in dem sie aufgerufen wird, und dann auf diese Informationen reagiert. Dies ist jedoch eindeutig gleichwertig (und analog dazu, dass Perl die übliche Überladung nicht buchstäblich zulässt, sondern eine Funktion nur ihre Argumente untersuchen kann). Darüber hinaus wird die zu Beginn dieser Antwort erwähnte zweideutige Situation gut gelöst. Perl beschwert sich nicht darüber, dass es nicht weiß, welche Methode es aufrufen soll. es nennt es einfach. Alles, was es tun muss, ist herauszufinden, in welchem ​​Kontext die Funktion aufgerufen wurde, was immer möglich ist:

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"

(Hinweis: Ich kann manchmal Perl-Operator sagen, wenn ich Funktion meine. Dies ist für diese Diskussion nicht entscheidend.)

Haskell verfolgt den anderen Ansatz, nämlich keine Nebenwirkungen zu haben. Es hat auch ein starkes Typsystem, so dass Sie Code wie folgt schreiben können:

main = do n <- readLn
          print (sqrt n) -- note that this is aligned below the n, if you care to run this

Dieser Code liest eine Gleitkommazahl aus der Standardeingabe und druckt ihre Quadratwurzel. Aber was ist daran überraschend? Nun, die Art von readLnist readLn :: Read a => IO a. Dies bedeutet, dass jeder Typ, der sein kann Read(formal jeder Typ, der eine Instanz der ReadTypklasse ist), readLnihn lesen kann. Woher wusste Haskell, dass ich eine Gleitkommazahl lesen wollte? Nun, die Art von sqrtist sqrt :: Floating a => a -> a, was im Wesentlichen bedeutet, dass sqrtnur Gleitkommazahlen als Eingaben akzeptiert werden können, und so folgerte Haskell, was ich wollte.

Was passiert, wenn Haskell nicht ableiten kann, was ich will? Nun, es gibt ein paar Möglichkeiten. Wenn ich den Rückgabewert überhaupt nicht verwende, ruft Haskell die Funktion einfach gar nicht erst auf. Wenn ich jedoch den Rückgabewert verwende, beschwert sich Haskell, dass der Typ nicht abgeleitet werden kann:

main = do n <- readLn
          print n
-- this program results in a compile-time error "Unresolved top-level overloading"

Ich kann die Mehrdeutigkeit beheben, indem ich den gewünschten Typ spezifiziere:

main = do n <- readLn
          print (n::Int)
-- this compiles (and does what I want)

Wie auch immer, diese ganze Diskussion bedeutet, dass eine Überladung durch den Rückgabewert möglich ist und durchgeführt wird, was einen Teil Ihrer Frage beantwortet.

Der andere Teil Ihrer Frage ist, warum mehr Sprachen dies nicht tun. Ich werde andere das beantworten lassen. Ein paar Anmerkungen: Der Hauptgrund ist wahrscheinlich, dass die Verwechslungsgefahr hier wirklich größer ist als bei der Überladung nach Argumenttyp. Sie können auch Rationales aus einzelnen Sprachen betrachten:

Ada : "Es scheint, dass die einfachste Überlastungsauflösungsregel darin besteht, alles - alle Informationen aus einem möglichst breiten Kontext - zu verwenden, um die überladene Referenz aufzulösen. Diese Regel mag einfach sein, ist aber nicht hilfreich. Sie erfordert den menschlichen Leser Wir glauben, dass eine bessere Regel eine ist, die die Aufgabe, die ein menschlicher Leser oder ein Compiler ausführen muss, explizit macht, und die diese Aufgabe macht, um beliebig große Textstücke zu scannen und willkürlich komplexe Schlussfolgerungen zu ziehen (wie (g) oben) so natürlich wie möglich für den menschlichen Leser. "

C ++ (Unterabschnitt 7.4.1 von Bjarne Stroustrups "The C ++ Programming Language"): "Rückgabetypen werden bei der Überlastungsauflösung nicht berücksichtigt. Der Grund besteht darin, die Auflösung für einen einzelnen Operator oder Funktionsaufruf kontextunabhängig zu halten. Beachten Sie:

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

Wenn der Rückgabetyp berücksichtigt würde, wäre es nicht mehr möglich, einen Aufruf von sqrt()isoliert zu betrachten und festzustellen, welche Funktion aufgerufen wurde. "(Beachten Sie zum Vergleich, dass es in Haskell keine impliziten Konvertierungen gibt.)

Java ( Java Language Specification 9.4.1 ): "Eine der geerbten Methoden muss für jede andere geerbte Methode durch einen Rückgabetyp ersetzt werden können. Andernfalls tritt ein Fehler bei der Kompilierung auf." (Ja, ich weiß, dass dies keine Begründung gibt. Ich bin sicher, dass die Begründung von Gosling in "der Java-Programmiersprache" gegeben wird. Vielleicht hat jemand eine Kopie? Ich wette, es ist im Wesentlichen das "Prinzip der geringsten Überraschung". ) Eine lustige Tatsache über Java: Die JVM ermöglicht das Überladen durch den Rückgabewert! Dies wird beispielsweise in Scala verwendet und kann auch direkt über Java aufgerufen werden, indem mit Interna herumgespielt wird.

PS. Abschließend ist es tatsächlich möglich, mit einem Trick den Rückgabewert in C ++ zu überladen. Zeuge:

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
}

Toller Beitrag, aber vielleicht möchten Sie klarstellen, was Lesen ist (String -> etwas).
Thomas Eding

In C ++ können Sie auch den von const / not const zurückgegebenen Wert überladen. stackoverflow.com/questions/251159/…
Geon

3
Für Ihren letzten Trick mit dem Überladen der Zwangsoperatoren funktioniert die Zeile "cout" manchmal, aber fast jede Änderung, die ich am Code vornehme, führt zu einer "mehrdeutigen Überladung für 'operator <<'".
Steve

1
Der Ansatz, den ich bevorzugen würde, wäre zu verlangen, dass eine Überlastung als "bevorzugt" markiert wird; Der Compiler würde zunächst nur mit bevorzugten Überladungen binden und dann feststellen, ob nicht bevorzugte Überladungen eine Verbesserung darstellen. Angenommen, Typen Foound Barunterstützen die bidirektionale Konvertierung, und eine Methode verwendet den Typ Foointern, gibt jedoch den Typ zurück Bar. Wenn eine solche Methode von einem Code aufgerufen wird, der das Ergebnis sofort zum Typ zwingt, funktioniert möglicherweise die FooVerwendung des BarRückgabetyps, aber der Fooeine wäre besser. Übrigens, ich würde auch gerne ein Mittel sehen, mit dem ...
Supercat

... eine Methode könnte festlegen, welcher Typ in einem Konstrukt wie verwendet werden soll var someVar = someMethod();(oder festlegen, dass seine Rückgabe nicht auf diese Weise verwendet werden soll). Beispielsweise könnte eine Typenfamilie, die eine Fluent-Schnittstelle implementiert, von veränderlichen und unveränderlichen Versionen profitieren. Sie var thing2 = thing1.WithX(3).WithY(5).WithZ(9);müsste also in ein veränderliches Objekt WithX(3)kopieren thing1, X mutieren und dieses veränderbare Objekt zurückgeben. WithY(5)würde Y mutieren und dasselbe Objekt zurückgeben; ebenfalls `WithZ (9). Dann würde die Zuordnung in einen unveränderlichen Typ konvertiert.
Supercat

37

Wenn Funktionen durch den Rückgabetyp überladen wurden und Sie diese beiden Überladungen hatten

int func();
string func();

Der Compiler kann auf keinen Fall herausfinden, welche dieser beiden Funktionen er aufrufen soll, wenn er einen solchen Aufruf sieht

void main() 
{
    func();
}

Aus diesem Grund verbieten Sprachdesigner häufig das Überladen von Rückgabewerten.

Einige Sprachen (wie MSIL), aber sie erlauben durch Rückgabetyp zu überlasten. Natürlich sind auch sie mit der oben genannten Schwierigkeit konfrontiert, aber es gibt Problemumgehungen, für die Sie ihre Dokumentation konsultieren müssen.


4
Ein kleiner Streitpunkt (Ihre Antwort liefert eine sehr klare, verständliche Begründung): Es ist nicht so, dass es keinen Weg gibt; Es ist nur so, dass die Wege ungeschickt und schmerzhafter wären, als die meisten Menschen es gerne hätten. In C ++ wäre die Überlastung wahrscheinlich mit einer hässlichen Cast-Syntax behoben worden.
Michael Burr

2
@ Jörg W Mittag: Sie sehen nicht, was die Funktionen tun. Sie könnten leicht verschiedene Nebenwirkungen haben.
A. Rex

2
@ Jörg - In den meisten gängigen Programmiersprachen (C / C ++, C #, Java usw.) haben Funktionen häufig Nebenwirkungen. In der Tat würde ich vermuten, dass Funktionen mit Nebenwirkungen mindestens so häufig sind wie solche ohne.
Michael Burr

6
Hier spät einspringen, aber in einigen Kontexten hat "Funktion" die enge Definition von (im Wesentlichen) "eine Methode ohne Nebenwirkungen". Umgangssprachlich wird "Funktion" häufig synonym mit "Methode" oder "Unterprogramm" verwendet. Jörg ist entweder rigoros oder pedantisch, abhängig von Ihrer Sichtweise :)
AwesomeTown

3
Einige Gesichtspunkte könnten noch später Adjektive als rigoros oder pedantisch verwenden
Patrick McDonald

27

Wie würden Sie in einer solchen Sprache Folgendes lösen:

f(g(x))

wenn fhatte Überlastungen void f(int)und void f(string)und ghatte Überlastungen int g(int)und string g(int)? Sie würden eine Art Disambiguator brauchen.

Ich denke, die Situationen, in denen Sie dies benötigen könnten, wären besser zu bedienen, wenn Sie einen neuen Namen für die Funktion wählen.


2
Die regelmäßige Art der Überlastung kann ebenfalls zu Mehrdeutigkeiten führen. Ich denke, diese Probleme werden normalerweise gelöst, indem die Anzahl der erforderlichen Casts gezählt wird, aber das funktioniert nicht immer.
Jay Conrod

1
Ja, Standard-Conversions werden in exakte Übereinstimmung, Promotion und Conversion eingeteilt: void f (int); nichtig f (lang); Fa'); ruft f (int) auf, da dies nur eine Beförderung ist, während die Konvertierung in long eine Konvertierung ist. void f (float); nichtig f (kurz); f (10); würde eine Konvertierung für beide erfordern: Der Anruf ist mehrdeutig.
Johannes Schaub - litb

Wenn die Sprache nur schleppend bewertet wird, ist dies kein so großes Problem.
Jdd

Upvote, das Zusammenspiel von Parametertypüberladung und Rückgabetypüberladung wird in Rex 'Beitrag nicht angesprochen. Sehr guter Punkt.
Joseph Garvin

1
Wenn ich eine Sprache entwerfe, lautet meine Regel, dass für jede überladene Funktion jede Parametersignatur einen Rückgabetyp als Standard haben muss. Ein Compiler würde zunächst davon ausgehen, dass jeder Funktionsaufruf den Standardtyp verwendet. Sobald dies jedoch geschehen ist, prüft der Compiler in jeder Situation, in der der Rückgabewert einer Funktion sofort umgewandelt oder zu etwas anderem gezwungen wird, auf eine Überladung, deren Parametersignatur identisch ist, deren Rückgabetyp jedoch besser übereinstimmt (oder möglicherweise ungültig ist). . Ich würde wahrscheinlich auch eine "Override-One - Override-All" -Regel für solche Überladungen auferlegen.
Supercat

19

So stehlen Sie eine C ++ - spezifische Antwort aus einer anderen sehr ähnlichen Frage (Dupe?):


Funktionsrückgabetypen spielen bei der Überlastungsauflösung keine Rolle, nur weil Stroustrup (ich nehme an, dass andere C ++ - Architekten Eingaben gemacht haben) wollte, dass die Überlastungsauflösung "kontextunabhängig" ist. Siehe 7.4.1 - "Überladen und Rückgabetyp" aus der "C ++ - Programmiersprache, dritte Ausgabe".

Der Grund besteht darin, die Auflösung für einen einzelnen Operator oder Funktionsaufruf kontextunabhängig zu halten.

Sie wollten, dass es nur darauf basiert, wie die Überladung aufgerufen wurde - nicht darauf, wie das Ergebnis verwendet wurde (wenn es überhaupt verwendet wurde). In der Tat werden viele Funktionen aufgerufen, ohne das Ergebnis zu verwenden, oder das Ergebnis würde als Teil eines größeren Ausdrucks verwendet. Ein Faktor, von dem ich sicher bin, dass er ins Spiel kam, als sie entschieden, dass wenn der Rückgabetyp Teil der Auflösung wäre, es viele Aufrufe überladener Funktionen geben würde, die mit komplexen Regeln aufgelöst werden müssten oder den Compiler auslösen müssten Ein Fehler, dass der Aufruf nicht eindeutig war.

Und, Gott weiß, die C ++ - Überlastungsauflösung ist derzeit komplex genug ...


5

In haskell ist dies möglich, obwohl es keine Funktionsüberladung gibt. Haskell verwendet Typklassen. In einem Programm konnte man sehen:

class Example a where
    example :: Integer -> a

instance Example Integer where  -- example is now implemented for Integer
    example :: Integer -> Integer
    example i = i * 10

Funktionsüberladung selbst ist nicht so beliebt. Die meisten Sprachen, die ich damit gesehen habe, sind C ++, vielleicht Java und / oder C #. In allen dynamischen Sprachen ist es eine Abkürzung für:

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

Deshalb macht es nicht viel Sinn. Die meisten Menschen sind nicht daran interessiert, ob die Sprache Ihnen dabei helfen kann, eine einzelne Zeile zu löschen, wo immer Sie sie verwenden.

Pattern Matching ähnelt in gewisser Weise der Funktionsüberladung, und ich denke, manchmal funktioniert es ähnlich. Dies ist jedoch nicht üblich, da es nur für wenige Programme nützlich und in den meisten Sprachen schwierig zu implementieren ist.

Sie sehen, es gibt unendlich viele andere, besser zu implementierende Funktionen, die in die Sprache implementiert werden können, darunter:

  • Dynamische Eingabe
  • Interne Unterstützung für Listen, Wörterbücher und Unicode-Zeichenfolgen
  • Optimierungen (JIT, Typinferenzierung, Kompilierung)
  • Integrierte Bereitstellungstools
  • Bibliotheksunterstützung
  • Community-Unterstützung und Treffpunkte
  • Reichhaltige Standardbibliotheken
  • Gute Syntax
  • Lesen Sie die eval-Druckschleife
  • Unterstützung für reflektierende Programmierung

3
Haskell ist überladen. Typklassen ist die Sprachfunktion, mit der überladene Funktionen definiert werden.
Lii

2

Gute Antworten! Insbesondere die Antwort von A.Rex ist sehr detailliert und lehrreich. Er weist darauf hin, C ++ nicht vom Benutzer bereitgestellte Typumwandlung Betreiber berücksichtigt bei der Kompilierung lhs = func(); (wo func wirklich der Name einer Struktur ist) . Meine Problemumgehung ist etwas anders - nicht besser, nur anders (obwohl sie auf derselben Grundidee basiert).

Während ich schreiben wollte ...

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++!

Am Ende hatte ich eine Lösung, die eine parametrisierte Struktur verwendet (mit T = dem Rückgabetyp):

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 :)

Ein Vorteil dieser Lösung besteht darin, dass jeder Code, der diese Vorlagendefinitionen enthält, mehr Spezialisierungen für mehr Typen hinzufügen kann. Sie können bei Bedarf auch Teilspezialisierungen der Struktur vornehmen. Wenn Sie beispielsweise eine spezielle Behandlung für Zeigertypen wünschen:

template <typename T>
struct func<T*>
{
    operator T*()
    { <<special handling for T*>> } 
};

Negativ kann man int x = func();mit meiner Lösung nicht schreiben . Du musst schreiben int x = func<int>();. Sie müssen explizit angeben, um welchen Rückgabetyp es sich handelt, anstatt den Compiler zu bitten, ihn anhand der Typkonvertierungsoperatoren zu ermitteln. Ich würde sagen, dass "meine" Lösung und die von A.Rex beide zu einer paretooptimalen Front gehören , um dieses C ++ - Dilemma anzugehen :)


1

Wenn Sie Methoden mit unterschiedlichen Rückgabetypen überladen möchten, fügen Sie einfach einen Dummy-Parameter mit dem Standardwert hinzu , um die Ausführung der Überladung zu ermöglichen. Vergessen Sie jedoch nicht, dass der Parametertyp unterschiedlich sein sollte, damit die Überladungslogik als Nächstes funktioniert.

type    
    myclass = class
    public
      function Funct1(dummy: string = EmptyStr): String; overload;
      function Funct1(dummy: Integer = -1): Integer; overload;
    end;

benutze es so

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;

Das ist eine schreckliche Idee. Führen Sie keine Dummy-Parameter ein, das ist ein großer Code-Geruch. Wählen Sie stattdessen andere Namen oder einen Rückgabetyp, der sich wie eine diskriminierte Vereinigung oder etwas anderes verhalten kann.
Abel

@Abel, was Sie vorschlagen, ist eigentlich die schreckliche Idee, denn die ganze Idee handelt von diesem Dummy-Parameter, und es wird so benannt, um dem Entwickler klar zu machen, dass dieser Parameter Dummy ist und ignoriert werden sollte, auch für Sie Ich weiß nicht, dass die Dummy-Parameter mit Standardwerten in vielen Bibliotheken, VCL in Delphi und vielen IDEs verwendet werden, z. B. in Delphi. Sie können sie in der sysutils-Einheit in SafeLoadLibrary sehen ...
ZORRO_BLANCO

Es gibt sicherlich Szenarien, in denen Dummy-Parameter nützlich sind, wie in Lambdas bei Karten- oder Falzoperationen oder bei der Implementierung einer Schnittstelle. Aber nur um eine Überlastung zu schaffen, nein, ich bin anderer Meinung. Es gibt keine Notwendigkeit und es ist Lärm, ohne den Programmierer leben können.
Abel

0

Wie bereits gezeigt, führen mehrdeutige Aufrufe einer Funktion, die sich nur durch den Rückgabetyp unterscheidet, zu Mehrdeutigkeiten. Mehrdeutigkeit führt zu fehlerhaftem Code. Fehlerhafter Code muss vermieden werden.

Die Komplexität, die durch den Versuch der Mehrdeutigkeit verursacht wird, zeigt, dass dies kein guter Hack ist. Abgesehen von einer intellektuellen Übung - warum nicht Verfahren mit Referenzparametern verwenden?

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

Weil Sie die "return" -Werte nicht einfach sofort wiederverwenden können. Sie doing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
müssten

0

Diese Überladungsfunktion ist nicht schwer zu verwalten, wenn Sie sie etwas anders betrachten. Folgendes berücksichtigen,

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

Wenn eine Sprache eine Überladung zurückgeben würde, würde dies eine Überladung der Parameter ermöglichen, jedoch keine Duplikationen. Dies würde das Problem lösen von:

main (){
f(x)
}

weil es nur ein f (int choice) zur Auswahl gibt.


0

In .NET verwenden wir manchmal einen Parameter, um die gewünschte Ausgabe eines generischen Ergebnisses anzugeben, und führen dann eine Konvertierung durch, um das zu erhalten, was wir erwarten.

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

Vielleicht könnte auch dieses Beispiel helfen:

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

1
Es ist etwas hackisch und kann zu Laufzeitfehlern führen, wenn der Benutzer das Ergebnis nicht richtig umwandelt oder wenn der Entwickler die Rückgabetypen nicht richtig mit der Aufzählung übereinstimmt. Ich würde empfehlen, einen vorlagenbasierten Ansatz (oder generische Parameter in C #?) Wie in dieser Antwort zu verwenden
sleblanc

0

Für die Aufzeichnung erlaubt Octave unterschiedliche Ergebnisse, je nachdem, ob das Rückgabeelement skalar oder Array ist.

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

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

Vgl. Auch Singular Value Decomposition .


0

Dieser ist für C ++ etwas anders; Ich weiß nicht, ob es als direkte Überladung durch den Rückgabetyp angesehen werden würde. Es ist eher eine Vorlagenspezialisierung, die sich wie folgt verhält.

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

In diesem Beispiel wird die Auflösung von Funktionsüberladungen nach Rückgabetyp nicht genau verwendet. Diese C ++ - Nichtobjektklasse verwendet jedoch eine Vorlagenspezialisierung, um die Auflösung von Funktionsüberlastungen nach Rückgabetyp mit einer privaten statischen Methode zu simulieren.

Jede der convertToTypeFunktionen ruft die Funktionsvorlage auf. stringToValue()Wenn Sie sich die Implementierungsdetails oder den Algorithmus dieser Funktionsvorlage ansehen, ruft sie auf getValue<T>( param, param )und gibt einen Typ zurück Tund speichert ihn in einem T*, der stringToValue()als einer seiner Parameter an die Funktionsvorlage übergeben wird .

Anders als so etwas; C ++ verfügt nicht wirklich über einen Mechanismus, mit dem die Auflösung von Funktionen nach Rückgabetyp überladen werden kann. Möglicherweise gibt es andere Konstrukte oder Mechanismen, von denen ich nicht weiß, dass sie die Auflösung nach Rückgabetyp simulieren könnten.


-1

Ich denke, dies ist eine Lücke in der modernen C ++ - Definition… warum?

int func();
double func();

// example 1. → defined
int i = func();

// example 2. → defined
double d = func();

// example 3. → NOT defined. error
void main() 
{
    func();
}

Warum kann ein C ++ - Compiler in Beispiel "3" keinen Fehler auslösen und den Code in Beispiel "1 + 2" akzeptieren?


Ja, das haben sie damals für C # (und vielleicht für C ++) in Betracht gezogen. Obwohl Ihr Code trivial ist, wird es sehr schnell sehr schnell zu entscheiden, welche Methode aufgelöst werden soll, wenn Sie Klassenhierarchien, virtuelle Methoden, Abstracts und Schnittstellen, andere Überladungen und manchmal Mehrfachvererbung hinzufügen. Es ist eine Entscheidung der Designer, diesen Weg nicht zu gehen, aber andere Sprachen haben sich auf verschiedenen Erfolgsebenen anders entschieden.
Abel

-2

Die meisten statischen Sprachen unterstützen jetzt auch Generika, die Ihr Problem lösen würden. Wie bereits erwähnt, gibt es ohne Parameterunterschiede keine Möglichkeit zu wissen, welche aufgerufen werden sollen. Wenn Sie dies tun möchten, verwenden Sie einfach Generika und nennen Sie es einen Tag.


Nicht dasselbe. Wie würden Sie mit einer Funktion umgehen, die Eingaben in eine Ganzzahl, einen Float, einen Bool oder was auch immer übersetzt, basierend darauf, wie der Rückgabetyp verwendet wird? Es kann nicht verallgemeinert werden, da Sie für jeden einen Sonderfall benötigen.
Jay Conrod

Unter codeproject.com/KB/cpp/returnoverload.aspx finden Sie eine clevere Strategie zum "Überladen bei Rückgabetyp". Anstatt eine Funktion func () zu definieren, definieren Sie im Grunde eine Struktur func, geben ihr einen operator () () und konvertieren sie in jeden geeigneten Typ.
j_random_hacker

Jay, Sie definieren den Rückgabetyp, wenn Sie die Funktion aufrufen. Wenn die Inpus unterschiedlich sind, gibt es überhaupt kein Problem. Wenn es dasselbe gibt, können Sie eine generische Version haben, die möglicherweise eine Logik hat, die auf dem Typ basiert, der GetType () verwendet.
Charles Graham
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.