Funzione sovraccarico per tipo di ritorno?


252

Perché le lingue tipicamente statiche più tradizionali non supportano il sovraccarico di funzioni / metodi per tipo di ritorno? Non riesco a pensare a nessuno che lo faccia. Sembra non meno utile o ragionevole del supporto del sovraccarico per tipo di parametro. Come mai è molto meno popolare?


Risposte:


523

Contrariamente a quanto dicono gli altri, il sovraccarico per tipo di ritorno è possibile ed è fatto da alcune lingue moderne. La solita obiezione è quella in codice come

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

non puoi dire quale func()viene chiamato. Questo può essere risolto in alcuni modi:

  1. Avere un metodo prevedibile per determinare quale funzione viene chiamata in tale situazione.
  2. Ogni volta che si verifica una situazione del genere, si tratta di un errore di compilazione. Tuttavia, avere una sintassi che consenta al programmatore di chiarire le ambiguità, ad es int main() { (string)func(); }.
  3. Non avere effetti collaterali. Se non si hanno effetti collaterali e non si utilizza mai il valore restituito di una funzione, il compilatore può evitare di chiamare la funzione in primo luogo.

Due delle lingue che uso regolarmente ( ab ) usano il sovraccarico per tipo di ritorno: Perl e Haskell . Lasciami descrivere cosa fanno.

In Perl , esiste una distinzione fondamentale tra contesto scalare e elenco (e altri, ma faremo finta che ce ne siano due). Ogni funzione integrata in Perl può fare cose diverse a seconda del contesto in cui viene chiamata. Ad esempio, l' joinoperatore forza il contesto dell'elenco (sulla cosa da unire) mentre l' scalaroperatore forza il contesto scalare, quindi confronta:

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.

Ogni operatore in Perl fa qualcosa in un contesto scalare e qualcosa nel contesto di un elenco, e possono essere diversi, come illustrato. (Questo non è solo per operatori casuali come localtime. Se si utilizza un array @anel contesto dell'elenco, restituisce l'array, mentre nel contesto scalare, restituisce il numero di elementi. Quindi, ad esempio, print @astampa gli elementi, mentre print 0+@astampa le dimensioni. ) Inoltre, ogni operatore può forzare un contesto, ad esempio +forze addizionali per un contesto scalare. Ogni voce nei man perlfuncdocumenti questo. Ad esempio, qui fa parte della voce relativa a glob EXPR:

Nel contesto dell'elenco, restituisce un elenco (possibilmente vuoto) di espansioni di nomi di file sul valore di EXPRuna shell Unix standard /bin/csh. In un contesto scalare, glob scorre attraverso tali espansioni di nomi di file, restituendo undef quando l'elenco è esaurito.

Ora, qual è la relazione tra elenco e contesto scalare? Bene, man perlfuncdice

Ricorda la seguente regola importante: Non esiste una regola che collega il comportamento di un'espressione nel contesto di elenco al suo comportamento nel contesto scalare o viceversa. Potrebbe fare due cose totalmente diverse. Ogni operatore e funzione decide quale tipo di valore sarebbe più appropriato restituire in un contesto scalare. Alcuni operatori restituiscono la lunghezza dell'elenco che sarebbe stato restituito nel contesto dell'elenco. Alcuni operatori restituiscono il primo valore nell'elenco. Alcuni operatori restituiscono l'ultimo valore nell'elenco. Alcuni operatori restituiscono un numero di operazioni riuscite. In generale, fanno quello che vuoi, a meno che tu non voglia coerenza.

quindi non è una semplice questione di avere un'unica funzione, e alla fine fai una semplice conversione. In effetti, ho scelto l' localtimeesempio per quel motivo.

Non sono solo i built-in che hanno questo comportamento. Qualsiasi utente può definire tale funzione utilizzando wantarray, che consente di distinguere tra elenco, scalare e contesto vuoto. Quindi, ad esempio, puoi decidere di non fare nulla se vieni chiamato in un contesto vuoto.

Ora, potresti lamentarti del fatto che questo non è un vero sovraccarico per valore restituito perché hai solo una funzione, a cui viene detto il contesto in cui viene chiamato e quindi agisce su tali informazioni. Tuttavia, questo è chiaramente equivalente (e analogo a come Perl non consenta letteralmente un sovraccarico normale, ma una funzione può semplicemente esaminare i suoi argomenti). Inoltre, risolve piacevolmente l'ambigua situazione menzionata all'inizio di questa risposta. Perl non si lamenta di non sapere quale metodo chiamare; lo chiama e basta. Tutto quello che deve fare è capire in quale contesto è stata chiamata la funzione, che è sempre possibile:

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"

(Nota: a volte posso dire operatore Perl quando intendo funzione. Questo non è cruciale per questa discussione.)

Haskell adotta l'altro approccio, vale a dire di non avere effetti collaterali. Ha anche un sistema di tipo forte, quindi puoi scrivere codice come il seguente:

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

Questo codice legge un numero in virgola mobile dall'input standard e stampa la sua radice quadrata. Ma cosa sorprende di questo? Bene, il tipo di readLnè readLn :: Read a => IO a. Ciò significa che per qualsiasi tipo che può essere Read(formalmente, ogni tipo che è un'istanza della Readclasse type), readLnpuò leggerlo. Come faceva Haskell a sapere che volevo leggere un numero in virgola mobile? Bene, il tipo di sqrtè sqrt :: Floating a => a -> a, che essenzialmente significa che sqrtpuò accettare solo numeri in virgola mobile come input, e quindi Haskell ha dedotto ciò che volevo.

Cosa succede quando Haskell non può dedurre ciò che voglio? Bene, ci sono alcune possibilità. Se non uso affatto il valore restituito, Haskell semplicemente non chiamerà la funzione in primo luogo. Tuttavia, se mi faccio usare il valore di ritorno, allora Haskell si lamenta che non si può dedurre il tipo:

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

Posso risolvere l'ambiguità specificando il tipo che desidero:

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

Comunque, ciò che significa tutta questa discussione è che il sovraccarico per valore di ritorno è possibile ed è fatto, il che risponde a una parte della tua domanda.

L'altra parte della tua domanda è perché più lingue non lo fanno. Lascerò che gli altri rispondano. Tuttavia, alcuni commenti: la ragione principale è probabilmente che l'opportunità di confusione è davvero maggiore qui che nel sovraccarico per tipo di argomento. Puoi anche guardare i razionali dalle singole lingue:

Ada : "Potrebbe sembrare che la regola di risoluzione del sovraccarico più semplice sia utilizzare tutto - tutte le informazioni da un contesto il più ampio possibile - per risolvere il riferimento sovraccarico. Questa regola può essere semplice, ma non è utile. Richiede il lettore umano per scansionare pezzi di testo arbitrariamente grandi e fare inferenze arbitrariamente complesse (come (g) sopra). Crediamo che una regola migliore sia quella che rende esplicito il compito che un lettore umano o un compilatore deve svolgere e che lo rende il più naturale possibile per il lettore umano ".

C ++ (sottosezione 7.4.1 di "Il linguaggio di programmazione C ++" di Bjarne Stroustrup): "I tipi di restituzione non sono considerati nella risoluzione di sovraccarico. Il motivo è mantenere la risoluzione per un singolo operatore o chiamata di funzione indipendente dal contesto. Considerare:

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

Se si prendesse in considerazione il tipo di ritorno, non sarebbe più possibile esaminare una chiamata sqrt()in isolamento e determinare quale funzione è stata chiamata. "(Nota, per confronto, che in Haskell non ci sono impliciti conversioni ).

Java ( Java Language Specification 9.4.1 ): "Uno dei metodi ereditati deve essere sostituibile con tipo restituito per ogni altro metodo ereditato, altrimenti si verifica un errore di compilazione." (Sì, lo so che questo non dà una logica. Sono sicuro che la logica è data da Gosling nel "linguaggio di programmazione Java". Forse qualcuno ne ha una copia? Scommetto che in sostanza è il "principio della minima sorpresa". ) Tuttavia, fatto divertente su Java: la JVM consente il sovraccarico in base al valore restituito! Questo è usato, ad esempio, in Scala , ed è possibile accedervi direttamente tramite Java anche giocando con gli interni.

PS. Come nota finale, è effettivamente possibile sovraccaricare il valore restituito in C ++ con un trucco. Testimone:

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
}

Ottimo post, ma potresti voler chiarire cos'è la lettura (String -> qualcosa).
Thomas Eding,

C ++ consente inoltre di sovraccaricare il valore restituito const / not const. stackoverflow.com/questions/251159/…
geon

3
Per il tuo ultimo trucco con il sovraccarico degli operatori di coercizione, la linea "cout" funziona a volte, ma quasi ogni modifica apportata al codice lo rende "ambiguo sovraccarico per" operatore << "".
Steve,

1
L'approccio che preferirei sarebbe richiedere che un sovraccarico sia contrassegnato come "preferito"; il compilatore inizierebbe eseguendo il binding utilizzando solo i sovraccarichi preferiti e quindi determinando se eventuali sovraccarichi non preferiti sarebbero un miglioramento. Tra le altre cose, i tipi Supponiamo che Fooe Barsupporto per la conversione bidirezionale e un metodo usi digitare Foointernamente ma tipo rendimenti Bar. Se tale metodo viene chiamato dal codice che costringe immediatamente il risultato a digitare Foo, l'utilizzo del Bartipo restituito potrebbe funzionare, ma Fooquello sarebbe migliore. A proposito, mi piacerebbe anche vedere un mezzo con cui ...
supercat

... un metodo potrebbe designare quale tipo dovrebbe essere usato in un costrutto simile var someVar = someMethod();(oppure designare che il suo ritorno non dovrebbe essere usato in questo modo). Ad esempio, una famiglia di tipi che implementa un'interfaccia Fluente potrebbe trarre vantaggio dall'avere versioni mutabili e immutabili, quindi var thing2 = thing1.WithX(3).WithY(5).WithZ(9);dovrebbe essere WithX(3)copiata thing1su un oggetto mutabile, mutare X e restituire quell'oggetto mutabile; WithY(5)muterebbe Y e restituirebbe lo stesso oggetto; allo stesso modo `WithZ (9). Quindi il compito verrebbe convertito in un tipo immutabile.
supercat

37

Se le funzioni sono state sovraccaricate dal tipo restituito e si hanno questi due sovraccarichi

int func();
string func();

non c'è modo che il compilatore possa capire quale di queste due funzioni chiamare quando vede una chiamata come questa

void main() 
{
    func();
}

Per questo motivo, i progettisti di linguaggi spesso vietano il sovraccarico del valore restituito.

Alcune lingue (come MSIL), tuttavia, non consentono di sovraccarico per tipo di ritorno. Ovviamente anche loro affrontano la difficoltà di cui sopra, ma hanno soluzioni alternative, per le quali dovrai consultare la loro documentazione.


4
Un piccolo cavillo (la tua risposta fornisce una logica molto chiara e comprensibile): non è che non ci sia modo; è solo che i modi sarebbero goffi e più dolorosi di quanto la maggior parte delle persone vorrebbe. Ad esempio, in C ++, il sovraccarico sarebbe stato probabilmente risolvibile usando una brutta sintassi del cast.
Michael Burr,

2
@ Jörg W Mittag: non vedi cosa fanno le funzioni. Potrebbero facilmente avere effetti collaterali diversi .
A. Rex,

2
@ Jörg - nella maggior parte dei linguaggi di programmazione tradizionali (C / C ++, C #, Java, ecc.) Le funzioni hanno comunemente effetti collaterali. In effetti, immagino che le funzioni con effetti collaterali siano almeno comuni quanto quelle senza.
Michael Burr,

6
Saltando in ritardo qui, ma in alcuni contesti "funzione" ha la definizione ristretta di (essenzialmente) "un metodo senza effetti collaterali". Più colloquialmente, la "funzione" è spesso usata in modo intercambiabile con "metodo" o "subroutine". Jorg è rigoroso o pedante, a seconda del tuo punto di vista :)
AwesomeTown,

3
Saltando ancora più tardi, alcuni punti di vista potrebbero usare aggettivi diversi da quelli rigorosi o pedanti
Patrick McDonald,

27

In una tale lingua, come risolveresti quanto segue:

f(g(x))

se faveva sovraccarichi void f(int)ed void f(string)e gaveva sovraccarichi int g(int)e string g(int)? Avresti bisogno di una sorta di chiarimento delle ambiguità.

Penso che le situazioni in cui potresti averne bisogno sarebbero meglio servite scegliendo un nuovo nome per la funzione.


2
Il tipo normale di sovraccarico può anche provocare ambiguità. Penso che questi vengano normalmente risolti contando il numero di cast richiesti, ma questo non sempre funziona.
Jay Conrod,

1
sì, le conversioni standard sono classificate in corrispondenza di corrispondenza, promozione e conversione esatte: void f (int); void f (long); fa'); chiama f (int), perché è solo una promozione, mentre la conversione in long è una conversione. void f (float); void f (corto); f (10); richiederebbe la conversione per entrambi: la chiamata è ambigua.
Johannes Schaub - litb

Se la lingua ha una valutazione pigra, questo non è un problema.
jdd,

Inoltre, l'interazione tra il sovraccarico del tipo di parametro e il sovraccarico del tipo di ritorno non è trattata nel post di Rex. Ottimo punto
Joseph Garvin,

1
Se stavo progettando una lingua, la mia regola sarebbe che per ogni funzione sovraccarica, ogni firma di parametro deve avere un tipo di ritorno designato come predefinito; un compilatore inizierà partendo dal presupposto che ogni chiamata di funzione utilizzerà il tipo predefinito. Una volta fatto ciò, tuttavia, in ogni situazione in cui il valore restituito di una funzione veniva immediatamente trasmesso o costretto a qualcos'altro, il compilatore avrebbe verificato un sovraccarico la cui firma del parametro è identica, ma il cui tipo restituito è una corrispondenza migliore (o possibilmente nulla) . Probabilmente importerei anche una regola "override-one - override-all" per tali sovraccarichi.
supercat

19

Per rubare una risposta specifica C ++ da un'altra domanda molto simile (dupe?):


I tipi di restituzione delle funzioni non entrano in gioco nella risoluzione del sovraccarico semplicemente perché Stroustrup (presumo con input di altri architetti C ++) voleva che la risoluzione del sovraccarico fosse "indipendente dal contesto". Vedere 7.4.1 - "Tipo di sovraccarico e restituzione" dal "Linguaggio di programmazione C ++, terza edizione".

Il motivo è mantenere la risoluzione per un singolo operatore o chiamata di funzione indipendente dal contesto.

Volevano che si basasse solo sul modo in cui veniva chiamato il sovraccarico, non sul modo in cui veniva usato il risultato (se usato). In effetti, molte funzioni vengono chiamate senza utilizzare il risultato o il risultato verrebbe utilizzato come parte di un'espressione più grande. Un fattore che sono sicuro che è entrato in gioco quando hanno deciso che era che se il tipo restituito fosse parte della risoluzione ci sarebbero state molte chiamate a funzioni sovraccariche che avrebbero dovuto essere risolte con regole complesse o avrebbero dovuto essere lanciate dal compilatore un errore che la chiamata era ambigua.

E, lo sa Lord, la risoluzione del sovraccarico in C ++ è abbastanza complessa così com'è ...


5

In haskell è possibile anche se non ha un sovraccarico di funzioni. Haskell utilizza le classi di tipi. In un programma potresti vedere:

class Example a where
    example :: Integer -> a

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

Il sovraccarico della funzione in sé non è così popolare. Principalmente le lingue che ho visto sono C ++, forse java e / o C #. In tutte le lingue dinamiche è una scorciatoia per:

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

Pertanto non ha molto senso. Alla maggior parte delle persone non interessa sapere se la lingua può aiutarti a eliminare una riga per ogni volta che la usi.

La corrispondenza dei modelli è in qualche modo simile al sovraccarico delle funzioni e immagino che a volte funzioni in modo simile. Non è comune, perché è utile solo per pochi programmi ed è difficile da implementare sulla maggior parte delle lingue.

Vedete, ci sono infinite altre funzioni migliori e più facili da implementare da implementare nel linguaggio, tra cui:

  • Digitazione dinamica
  • Supporto interno per liste, dizionari e stringhe unicode
  • Ottimizzazioni (JIT, inferenza dei tipi, compilazione)
  • Strumenti di implementazione integrati
  • Supporto per la biblioteca
  • Sostegno della comunità e luoghi di raccolta
  • Librerie standard ricche
  • Buona sintassi
  • Leggi il ciclo di stampa eval
  • Supporto per la programmazione riflessiva

3
Haskell ha un sovraccarico. Classi di tipo è la funzione del linguaggio utilizzata per definire le funzioni sovraccaricate.
Lii,

2

Buone risposte! La risposta di A.Rex in particolare è molto dettagliata e istruttiva. Come fa notare, C ++ non considera gli operatori tipo di conversione forniti dall'utente al momento della compilazione lhs = func(); (dove func è in realtà il nome di una struttura) . La mia soluzione è un po 'diversa - non migliore, solo diversa (anche se si basa sulla stessa idea di base).

Considerando che avrei voluto scrivere ...

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

Ho finito con una soluzione che utilizza una struttura parametrizzata (con T = il tipo restituito):

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

Un vantaggio di questa soluzione è che qualsiasi codice che include queste definizioni di modello può aggiungere più specializzazioni per più tipi. Inoltre, è possibile eseguire specializzazioni parziali della struttura secondo necessità. Ad esempio, se si desidera una gestione speciale per i tipi di puntatore:

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

Come negativo, non puoi scrivere int x = func(); con la mia soluzione. Si deve scrivere int x = func<int>();. Devi dire esplicitamente qual è il tipo restituito, piuttosto che chiedere al compilatore di sostenerlo guardando gli operatori di conversione del tipo. Direi che la "mia" soluzione e quelle di A.Rex appartengono entrambe a un fronte pareto-ottimale di modi per affrontare questo dilemma in C ++ :)


1

se si desidera sovraccaricare i metodi con diversi tipi di ritorno, è sufficiente aggiungere un parametro fittizio con valore predefinito per consentire l'esecuzione del sovraccarico, ma non dimenticare che il tipo di parametro dovrebbe essere diverso, quindi la logica di sovraccarico che funziona dopo è un esempio su delphi:

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

usalo così

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;

È un'idea terribile. Non introdurre parametri fittizi, è un grande odore di codice. Invece, scegli nomi diversi o scegli un tipo di ritorno che può agire come, o è un'unione discriminata o qualcosa del genere.
Abel,

@Abel quello che stai suggerendo è in realtà l'idea terribile, perché l'intera idea riguarda questo parametro fittizio, ed è così chiamato per chiarire allo sviluppatore che questo parametro è fittizio e dovrebbe essere ignorato, anche nel caso tu non so che i parametri fittizi con valori predefiniti sono usati in molte librerie, VCL in delphi e molti IDE, ad esempio in delphi puoi vederlo nell'unità sysutils in SafeLoadLibrary ...
ZORRO_BLANCO

Esistono certamente scenari in cui i parametri fittizi sono utili, come nelle lambda nelle operazioni map o fold, o durante l'implementazione di un'interfaccia. Ma per il solo scopo di creare un sovraccarico, no, prego di non essere d'accordo. Non è necessario ed è il rumore che i programmatori possono vivere senza.
Abel

0

Come già mostrato, le chiamate ambigue di una funzione che differisce solo per il tipo restituito introducono ambiguità. L'ambiguità induce codice difettoso. Il codice difettoso deve essere evitato.

La complessità guidata dal tentativo di ambiguità mostra che questo non è un buon trucco. Oltre a un esercizio intellettuale, perché non utilizzare procedure con parametri di riferimento.

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

Perché non è possibile riutilizzare facilmente i valori "return" immediatamente. Dovresti fare ogni chiamata su una sola linea, al contrario didoing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
Adowrath,

0

questa funzione di sovraccarico non è difficile da gestire, se la guardi in un modo leggermente diverso. considerare quanto segue,

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

se un linguaggio restituisse un sovraccarico, consentirebbe un sovraccarico dei parametri, ma non duplicazioni. questo risolverebbe il problema di:

main (){
f(x)
}

perché c'è solo una f (int scelta) tra cui scegliere.


0

In .NET, a volte utilizziamo un parametro per indicare l'output desiderato da un risultato generico, quindi eseguiamo una conversione per ottenere ciò che ci aspettiamo.

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

Forse questo esempio potrebbe aiutare anche:

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
È un tipo di hacking e potrebbe causare errori di runtime se l'utente non esegue correttamente il cast del risultato o se lo sviluppatore non abbina correttamente i tipi restituiti con l'enum. Consiglierei di usare un approccio basato su template (o parametri generici in C #?) Come in questa risposta
sleblanc

0

Per la cronaca, Octave consente risultati diversi in base all'elemento return che è scalare rispetto all'array.

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

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

Cfr. Anche la decomposizione del valore singolare .


0

Questo è leggermente diverso per C ++; Non so se sarebbe considerato sovraccarico direttamente dal tipo restituito. È più di una specializzazione modello che agisce nel modo di.

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

Questo esempio non utilizza esattamente la risoluzione di sovraccarico della funzione per tipo di ritorno, tuttavia questa classe di oggetti c ++ non utilizza la specializzazione del modello per simulare la risoluzione di sovraccarico della funzione per tipo di ritorno con un metodo statico privato.

Ciascuna delle convertToTypefunzioni chiama il modello di funzione stringToValue()e se si esaminano i dettagli di implementazione o l'algoritmo di questo modello di funzione, sta chiamando getValue<T>( param, param )e sta tornando indietro un tipo Te lo memorizza in un modello T*che viene passato nel stringToValue()modello di funzione come uno dei suoi parametri .

Altro che qualcosa del genere; Il C ++ in realtà non ha un meccanismo per avere una funzione che sovraccarica la risoluzione per tipo di ritorno. Potrebbero esserci altri costrutti o meccanismi di cui non sono a conoscenza che potrebbero simulare la risoluzione in base al tipo restituito.


-1

Penso che questo sia un GAP nella moderna definizione C ++ ... perché?

int func();
double func();

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

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

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

Perché un compilatore C ++ non può generare un errore nell'esempio "3" e accettare il codice nell'esempio "1 + 2" ??


Sì, è quello che hanno preso in considerazione al momento per C # (e forse C ++). Ma mentre il tuo codice è banale, una volta aggiunte gerarchie di classi, metodi virtuali, abstract e interfacce, altri sovraccarichi e, a volte ereditarietà multipla, diventa molto complesso decidere molto rapidamente quale metodo deve essere risolto. È una scelta dei progettisti non seguire questa strada, ma altre lingue hanno deciso diversamente a vari livelli di successo.
Abel,

-2

La maggior parte dei linguaggi statici ora supporta anche generici, che risolverebbero il tuo problema. Come affermato in precedenza, senza differenze nei parametri, non è possibile sapere quale chiamare. Quindi, se vuoi farlo, usa solo generici e chiamalo un giorno.


Non è la stessa cosa Come gestiresti una funzione che traduce l'input in un numero intero, float, bool o qualunque cosa in base a come viene utilizzato il tipo restituito? Non può essere generalizzato poiché è necessario un caso speciale per ciascuno.
Jay Conrod,

Vedi codeproject.com/KB/cpp/returnoverload.aspx per una strategia intelligente per "sovraccarico sul tipo di ritorno". Fondamentalmente, invece di definire una funzione func (), si definisce una struttura func, si dà un operatore () () e le conversioni a ciascun tipo appropriato.
j_random_hacker,

Jay, definisci il tipo di ritorno quando chiami la funzione. Se gli inpus sono diversi, allora non c'è alcun problema. Se sono uguali, puoi avere una versione generica che potrebbe avere una logica basata sul tipo usando GetType ().
Charles Graham,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.