Surcharge de fonction par type de retour?


252

Pourquoi les langages à typage statique plus courants ne prennent-ils pas en charge la surcharge de fonctions / méthodes par type de retour? Je ne peux penser à rien de tout cela. Cela ne semble pas moins utile ou raisonnable que de prendre en charge la surcharge par type de paramètre. Comment se fait-il qu'il soit tellement moins populaire?


Réponses:


523

Contrairement à ce que disent les autres, la surcharge par type de retour est possible et se fait par certains langages modernes. L'objection habituelle est qu'en code comme

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

vous ne pouvez pas dire qui func()est appelé. Cela peut être résolu de plusieurs manières:

  1. Avoir une méthode prévisible pour déterminer quelle fonction est appelée dans une telle situation.
  2. Chaque fois qu'une telle situation se produit, c'est une erreur de compilation. Cependant, avoir une syntaxe qui permet au programmeur de lever l'ambiguïté, par exemple int main() { (string)func(); }.
  3. N'ayez pas d'effets secondaires. Si vous n'avez pas d'effets secondaires et que vous n'utilisez jamais la valeur de retour d'une fonction, le compilateur peut éviter d'appeler la fonction en premier lieu.

Deux des langues que j'utilise régulièrement ( ab ) utilisent la surcharge par type de retour: Perl et Haskell . Permettez-moi de décrire ce qu'ils font.

En Perl , il existe une distinction fondamentale entre le contexte scalaire et le contexte de liste (et d'autres, mais nous prétendons qu'il y en a deux). Chaque fonction intégrée dans Perl peut faire des choses différentes selon le contexte dans lequel elle est appelée. Par exemple, l' joinopérateur force le contexte de liste (sur la chose à joindre) tandis que l' scalaropérateur force le contexte scalaire, alors comparez:

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.

Chaque opérateur en Perl fait quelque chose dans un contexte scalaire et quelque chose dans un contexte de liste, et ils peuvent être différents, comme illustré. (Ce n'est pas seulement pour des opérateurs aléatoires comme localtime. Si vous utilisez un tableau @adans un contexte de liste, il renvoie le tableau, tandis que dans un contexte scalaire, il retourne le nombre d'éléments. Ainsi, par exemple, print @aimprime les éléments, tout en print 0+@aimprime la taille. ) De plus, chaque opérateur peut forcer un contexte, par exemple l'addition +force le contexte scalaire. Chaque entrée dans les man perlfuncdocuments cela. Par exemple, voici une partie de l'entrée pour glob EXPR:

Dans un contexte de liste, renvoie une liste (éventuellement vide) d'extensions de nom de fichier sur la valeur de EXPRce que /bin/cshferait le shell Unix standard . Dans un contexte scalaire, glob itère à travers de telles extensions de nom de fichier, retournant undef lorsque la liste est épuisée.

Maintenant, quelle est la relation entre la liste et le contexte scalaire? Eh bien, man perlfuncdit

Souvenez-vous de la règle importante suivante: Il n'y a aucune règle qui relie le comportement d'une expression dans un contexte de liste à son comportement dans un contexte scalaire, ou vice versa. Cela pourrait faire deux choses totalement différentes. Chaque opérateur et fonction décide du type de valeur qu'il serait le plus approprié de renvoyer dans un contexte scalaire. Certains opérateurs renvoient la longueur de la liste qui aurait été retournée dans le contexte de la liste. Certains opérateurs renvoient la première valeur de la liste. Certains opérateurs renvoient la dernière valeur de la liste. Certains opérateurs renvoient un décompte d'opérations réussies. En général, ils font ce que vous voulez, sauf si vous voulez de la cohérence.

ce n'est donc pas une simple question d'avoir une seule fonction, puis vous effectuez une conversion simple à la fin. En fait, j'ai choisi l' localtimeexemple pour cette raison.

Ce ne sont pas seulement les fonctions intégrées qui ont ce comportement. Tout utilisateur peut définir une telle fonction à l'aide de wantarray, ce qui vous permet de faire la distinction entre le contexte liste, scalaire et vide. Ainsi, par exemple, vous pouvez décider de ne rien faire si vous êtes appelé dans un contexte nul.

Maintenant, vous pouvez vous plaindre que ce n'est pas une surcharge réelle par valeur de retour car vous n'avez qu'une seule fonction, qui est informée du contexte dans lequel elle est appelée, puis agit sur ces informations. Cependant, c'est clairement équivalent (et analogue à la façon dont Perl ne permet pas la surcharge habituelle littéralement, mais une fonction peut simplement examiner ses arguments). De plus, cela résout bien la situation ambiguë mentionnée au début de cette réponse. Perl ne se plaint pas de ne pas savoir quelle méthode appeler; il l'appelle juste. Il suffit de déterminer dans quel contexte la fonction a été appelée, ce qui est toujours 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"

(Remarque: je peux parfois dire opérateur Perl quand je veux dire fonction. Ce n'est pas crucial pour cette discussion.)

Haskell adopte l'autre approche, à savoir de ne pas avoir d'effets secondaires. Il possède également un système de type fort, et vous pouvez donc écrire du code comme suit:

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

Ce code lit un nombre à virgule flottante à partir de l'entrée standard et imprime sa racine carrée. Mais qu'est-ce qui est surprenant? Eh bien, le type readLnest readLn :: Read a => IO a. Cela signifie que pour tout type qui peut être Read(formellement, chaque type qui est une instance de la Readclasse de type), il readLnpeut le lire. Comment Haskell a-t-il su que je voulais lire un nombre à virgule flottante? Eh bien, le type de sqrtest sqrt :: Floating a => a -> a, ce qui signifie essentiellement qu'il sqrtne peut accepter que des nombres à virgule flottante comme entrées, et Haskell a donc déduit ce que je voulais.

Que se passe-t-il lorsque Haskell ne peut pas déduire ce que je veux? Eh bien, il y a quelques possibilités. Si je n'utilise pas du tout la valeur de retour, Haskell n'appellera tout simplement pas la fonction en premier lieu. Cependant, si je fais utiliser la valeur de retour, alors Haskell se plaindra qu'il ne peut pas déduire le type:

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

Je peux résoudre l'ambiguïté en spécifiant le type que je veux:

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

Quoi qu'il en soit, toute cette discussion signifie que la surcharge par valeur de retour est possible et est effectuée, ce qui répond en partie à votre question.

L'autre partie de votre question est pourquoi plus de langues ne le font pas. Je laisserai les autres répondre à cela. Cependant, quelques commentaires: la raison principale est probablement que l'opportunité de confusion est vraiment plus grande ici que dans la surcharge par type d'argument. Vous pouvez également consulter les justifications des différentes langues:

Ada : "Il peut sembler que la règle de résolution de surcharge la plus simple consiste à tout utiliser - toutes les informations d'un contexte aussi large que possible - pour résoudre la référence surchargée. Cette règle peut être simple, mais elle n'est pas utile. Elle nécessite le lecteur humain pour analyser des morceaux de texte arbitrairement grands et pour faire des inférences arbitrairement complexes (comme (g) ci-dessus). Nous pensons qu'une meilleure règle est celle qui rend explicite la tâche qu'un lecteur humain ou un compilateur doit effectuer, et qui rend cette tâche aussi naturel que possible pour le lecteur humain. "

C ++ (sous-section 7.4.1 du «langage de programmation C ++» de Bjarne Stroustrup): «Les types de retour ne sont pas pris en compte dans la résolution de surcharge. La raison est de garder la résolution pour un opérateur individuel ou un appel de fonction indépendant du contexte. Considérez:

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

Si le type de retour était pris en compte, il ne serait plus possible de regarder un appel de sqrt()isolément et de déterminer quelle fonction a été appelée. "(Notez, à titre de comparaison, que dans Haskell, il n'y a pas de conversions implicites .)

Java ( Java Language Specification 9.4.1 ): "L'une des méthodes héritées doit être substituable par type de retour pour chaque autre méthode héritée, sinon une erreur de compilation se produit." (Oui, je sais que cela ne donne aucune justification. Je suis sûr que la justification est donnée par Gosling dans "Java Programming Language". Peut-être que quelqu'un en a une copie? Je parie que c'est le "principe de la moindre surprise" en substance. Cependant, fait amusant sur Java: la JVM permet la surcharge par valeur de retour! Ceci est utilisé, par exemple, dans Scala , et est également accessible directement via Java en jouant avec des internes.

PS. Enfin, il est possible de surcharger par valeur de retour en C ++ avec une astuce. Témoin:

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
}

Excellent article, mais vous voudrez peut-être clarifier ce qu'est la lecture (String -> quelque chose).
Thomas Eding du

C ++ vous permet également de surcharger par la valeur renvoyée const / not const. stackoverflow.com/questions/251159/…
geon

3
Pour votre dernière astuce avec la surcharge des opérateurs de coercition, la ligne "cout" fonctionne parfois, mais presque tout changement que j'apporte au code lui donne une "surcharge ambiguë pour 'operator <<'".
Steve

1
L'approche que je privilégierais serait d'exiger qu'une surcharge soit marquée comme «préférée»; le compilateur commencerait par lier en utilisant uniquement les surcharges préférées, puis déterminerait si des surcharges non préférées seraient une amélioration. Entre autres choses, les types supposons Fooet Barsoutien conversion bidirectionnelle, et une méthode utilise le type Foointerne mais le type de retour Bar. Si une telle méthode est appelée par un code qui contraindra immédiatement le résultat à taper Foo, utiliser le Bartype de retour pourrait fonctionner, mais celui- Fooci serait mieux. BTW, j'aimerais aussi voir un moyen par lequel ...
supercat

... une méthode pourrait désigner quel type devrait être utilisé dans une construction comme var someVar = someMethod();(ou bien désigner que son retour ne devrait pas être utilisé de cette façon). Par exemple, une famille de types qui implémente une interface Fluent pourrait bénéficier de versions mutables et immuables, donc var thing2 = thing1.WithX(3).WithY(5).WithZ(9);aurait une WithX(3)copie thing1vers un objet mutable, mutera X et retournerait cet objet mutable; WithY(5)muterait Y et retournerait le même objet; de même `WithZ (9). Ensuite, l'affectation se convertirait en un type immuable.
supercat

37

Si les fonctions étaient surchargées par le type de retour et que vous aviez ces deux surcharges

int func();
string func();

il n'y a aucun moyen que le compilateur puisse déterminer laquelle de ces deux fonctions appeler en voyant un appel comme celui-ci

void main() 
{
    func();
}

Pour cette raison, les concepteurs de langage interdisent souvent la surcharge de valeur de retour.

Certaines langues (comme MSIL), cependant, ne permettent la surcharge par type de retour. Ils sont également confrontés à la difficulté ci-dessus, mais ils ont des solutions de contournement, pour lesquelles vous devrez consulter leur documentation.


4
Un petit problème (votre réponse donne une justification très claire et compréhensible): ce n'est pas qu'il n'y a aucun moyen; c'est juste que les moyens seraient maladroits et plus douloureux que la plupart des gens ne le souhaiteraient. Par exemple, en C ++, la surcharge aurait probablement pu être résolue en utilisant une syntaxe de conversion laide.
Michael Burr

2
@ Jörg W Mittag: Vous ne voyez pas ce que font les fonctions. Ils pourraient facilement avoir différents effets secondaires.
A. Rex

2
@ Jörg - dans la plupart des langages de programmation courants (C / C ++, C #, Java, etc.), les fonctions ont généralement des effets secondaires. En fait, je suppose que les fonctions avec effets secondaires sont au moins aussi courantes que celles sans.
Michael Burr,

6
Sauter tard ici, mais dans certains contextes, la "fonction" a la définition étroite de (essentiellement) "une méthode sans effets secondaires". Plus familièrement, «fonction» est souvent utilisé de manière interchangeable avec «méthode» ou «sous-programme». Jorg est soit rigoureux soit pédant, selon votre point de vue :)
AwesomeTown

3
Sautant encore plus tard, certains points de vue pourraient utiliser des adjectifs autres que rigoureux ou pédants
Patrick McDonald

27

Dans une telle langue, comment résoudriez-vous les problèmes suivants:

f(g(x))

si favait des surcharges void f(int)et void f(string)et gavait des surcharges int g(int)et string g(int)? Vous auriez besoin d'une sorte d'ambiguïté.

Je pense que les situations où vous pourriez en avoir besoin seraient mieux servies en choisissant un nouveau nom pour la fonction.


2
Le type régulier de surcharge peut également entraîner des ambiguïtés. Je pense que ceux-ci sont normalement résolus en comptant le nombre de lancers requis, mais cela ne fonctionne pas toujours.
Jay Conrod

1
oui, les conversions standard sont classées en correspondance exacte, promotion et conversion: void f (int); vide f (long); FA'); appelle f (int), car ce n'est qu'une promotion, alors que la conversion en long est une conversion. void f (float); void f (court); f (10); nécessiterait une conversion pour les deux: l'appel est ambigu.
Johannes Schaub - litb

Si la langue a une évaluation paresseuse, ce n'est pas autant un problème.
2010 à 5h08

Upvote, l'interaction de la surcharge de type de paramètre et de la surcharge de type de retour n'est pas abordée dans la publication de Rex. Très bon point.
Joseph Garvin

1
Si je concevais un langage, ma règle serait que pour toute fonction surchargée, chaque signature de paramètre doit avoir un type de retour désigné comme valeur par défaut; un compilateur commencerait par supposer que chaque appel de fonction utiliserait le type par défaut. Une fois que cela a été fait, cependant, dans chaque situation où la valeur de retour d'une fonction a été immédiatement castée ou forcée à quelque chose d'autre, le compilateur vérifierait une surcharge dont la signature de paramètre est identique, mais dont le type de retour est une meilleure correspondance (ou peut-être nulle) . J'imposerais probablement aussi une règle "remplacer un - remplacer tout" pour de telles surcharges.
supercat

19

Pour voler une réponse spécifique C ++ à une autre question très similaire (dupe?):


Les types de retour de fonction n'entrent pas en jeu dans la résolution de surcharge simplement parce que Stroustrup (je suppose avec l'entrée d'autres architectes C ++) voulait que la résolution de surcharge soit «indépendante du contexte». Voir 7.4.1 - «Surcharge et type de retour» du «Langage de programmation C ++, troisième édition».

La raison est de garder la résolution d'un opérateur individuel ou d'un appel de fonction indépendant du contexte.

Ils voulaient que cela soit basé uniquement sur la façon dont la surcharge était appelée - pas sur la façon dont le résultat était utilisé (s'il l'était). En effet, de nombreuses fonctions sont appelées sans utiliser le résultat ou le résultat serait utilisé dans le cadre d'une expression plus large. Un facteur qui, j'en suis sûr, est entré en jeu lorsqu'ils ont décidé que c'était le cas si le type de retour faisait partie de la résolution, il y aurait de nombreux appels à des fonctions surchargées qui devraient être résolues avec des règles complexes ou nécessiteraient le lancement du compilateur. une erreur que l'appel était ambigu.

Et, Seigneur le sait, la résolution de surcharge C ++ est suffisamment complexe en l'état ...


5

Dans haskell, c'est possible même s'il n'a pas de surcharge de fonction. Haskell utilise des classes de types. Dans un programme, vous pouvez voir:

class Example a where
    example :: Integer -> a

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

La surcharge de fonction elle-même n'est pas si populaire. La plupart des langages que j'ai vus avec sont C ++, peut-être java et / ou C #. Dans toutes les langues dynamiques, c'est un raccourci pour:

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

Par conséquent, cela ne sert à rien. La plupart des gens ne sont pas intéressés à savoir si la langue peut vous aider à supprimer une seule ligne, peu importe où vous l'utilisez.

La correspondance de modèles est quelque peu similaire à la surcharge de fonctions, et je suppose que cela fonctionne parfois de la même manière. Ce n'est pas courant car il n'est utile que pour quelques programmes et est difficile à implémenter dans la plupart des langues.

Vous voyez, il existe infiniment d'autres fonctionnalités plus faciles à implémenter à implémenter dans le langage, notamment:

  • Typage dynamique
  • Prise en charge interne des listes, dictionnaires et chaînes unicode
  • Optimisations (JIT, inférence de type, compilation)
  • Outils de déploiement intégrés
  • Prise en charge de la bibliothèque
  • Soutien communautaire et lieux de rassemblement
  • Bibliothèques standard riches
  • Bonne syntaxe
  • Lire la boucle d'impression eval
  • Prise en charge de la programmation réfléchissante

3
Haskell a une surcharge. Les classes de type sont la fonction de langage utilisée pour définir les fonctions surchargées.
Lii

2

Bonnes réponses! La réponse d'A .Rex en particulier est très détaillée et instructive. Comme il le fait remarquer, C ++ prend en compte les opérateurs de conversion de type fournis par l'utilisateur lors de la compilation lhs = func(); (où func est vraiment le nom d'une structure) . Ma solution de contournement est un peu différente - pas meilleure, juste différente (bien qu'elle soit basée sur la même idée de base).

Alors que j'avais voulu écrire ...

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

Je me suis retrouvé avec une solution qui utilise une structure paramétrée (avec T = le type de retour):

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 avantage de cette solution est que tout code qui inclut ces définitions de modèle peut ajouter plus de spécialisations pour plus de types. Vous pouvez également effectuer des spécialisations partielles de la structure selon vos besoins. Par exemple, si vous vouliez une gestion spéciale pour les types de pointeurs:

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

En négatif, vous ne pouvez pas écrire int x = func();avec ma solution. Vous devez écrire int x = func<int>();. Vous devez dire explicitement quel est le type de retour, plutôt que de demander au compilateur de le vérifier en regardant les opérateurs de conversion de type. Je dirais que «ma» solution et A.Rex appartiennent toutes les deux à un front pareto-optimal pour résoudre ce dilemme C ++ :)


1

si vous voulez surcharger des méthodes avec différents types de retour, ajoutez simplement un paramètre factice avec une valeur par défaut pour permettre l'exécution de la surcharge, mais n'oubliez pas que le type de paramètre doit être différent afin que la logique de surcharge fonctionne ensuite est par exemple sur delphi:

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

l'utiliser comme ça

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;

C'est une terrible idée. N'introduisez pas de paramètres fictifs, c'est une grosse odeur de code. Au lieu de cela, choisissez des noms différents, ou choisissez un type de retour qui peut agir comme, ou est une union discriminée ou quelque chose.
Abel

@Abel ce que vous proposez est en fait la terrible idée, car l'idée est à propos de ce paramètre factice, et il est nommé ainsi pour qu'il soit clair pour le développeur que ce paramètre est factice et doit être ignoré, même si vous Je ne sais pas les paramètres factices avec des valeurs par défaut sont utilisés dans de nombreuses bibliothèques, VCL dans delphi et de nombreux IDE, par exemple dans delphi vous pouvez le voir dans l'unité sysutils dans SafeLoadLibrary ...
ZORRO_BLANCO

Il existe certainement des scénarios où les paramètres factices sont utiles, comme dans les lambdas dans les opérations de carte ou de pliage, ou lors de la mise en œuvre d'une interface. Mais pour le simple plaisir de créer une surcharge, non, je vous prie de ne pas être d'accord. Il n'y a pas besoin et c'est du bruit dont les programmeurs peuvent se passer.
Abel

0

Comme déjà montré - les appels ambigus d'une fonction qui ne diffère que par le type de retour introduit une ambiguïté. L'ambiguïté induit un code défectueux. Un code défectueux doit être évité.

La complexité motivée par la tentative d'ambiguïté montre que ce n'est pas un bon hack. Outre un exercice intellectuel - pourquoi ne pas utiliser des procédures avec des paramètres de référence.

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

Parce que vous ne pouvez pas facilement réutiliser immédiatement les valeurs de "retour". Vous devriez faire chaque appel sur une seule ligne, par opposition àdoing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
Adowrath

0

cette fonctionnalité de surcharge n'est pas difficile à gérer, si vous la regardez d'une manière légèrement différente. considérer ce qui suit,

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

si une langue renvoyait une surcharge, elle autoriserait une surcharge de paramètres, mais pas des duplications. cela résoudrait le problème de:

main (){
f(x)
}

car il n'y a qu'un seul f (int choix) à choisir.


0

Dans .NET, nous utilisons parfois un paramètre pour indiquer la sortie souhaitée à partir d'un résultat générique, puis nous effectuons une conversion pour obtenir ce que nous attendons.

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

Peut-être que cet exemple pourrait aussi aider:

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
C'est une sorte de piratage, et pourrait entraîner des erreurs d'exécution si l'utilisateur ne transforme pas correctement le résultat, ou si le développeur ne correspond pas correctement aux types de retour avec l'énumération. Je recommanderais d'utiliser une approche basée sur un modèle (ou des paramètres génériques en C #?) Comme dans cette réponse
sleblanc

0

Pour mémoire, Octave permet un résultat différent selon que l'élément de retour est scalaire vs tableau.

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

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

Cf également Décomposition en valeurs singulières .


0

Celui-ci est légèrement différent pour C ++; Je ne sais pas si cela serait considéré comme une surcharge par type de retour directement. Il s'agit plutôt d'une spécialisation de modèle qui agit à la manière de.

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

Cet exemple n'utilise pas exactement la résolution de surcharge de fonction par type de retour, mais cette classe non objet c ++ utilise la spécialisation de modèle pour simuler la résolution de surcharge de fonction par type de retour avec une méthode statique privée.

Chacune des convertToTypefonctions appelle le modèle de fonction stringToValue()et si vous regardez les détails d'implémentation ou l'algorithme de ce modèle de fonction, il appelle getValue<T>( param, param )et retourne un type Tet le stocke dans un T*qui est passé dans le stringToValue()modèle de fonction comme l'un de ses paramètres. .

Autre que quelque chose comme ça; C ++ n'a pas vraiment de mécanisme pour avoir une résolution de surcharge de fonction par type de retour. Il peut y avoir d'autres constructions ou mécanismes que je ne connais pas qui pourraient simuler la résolution par type de retour.


-1

Je pense que c'est un GAP dans la définition C ++ moderne… pourquoi?

int func();
double func();

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

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

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

Pourquoi un compilateur C ++ ne peut-il pas générer d'erreur dans l'exemple "3" et accepter le code dans l'exemple "1 + 2" ??


Oui, c'est ce qu'ils envisageaient à l'époque pour C # (et peut-être C ++). Mais bien que votre code soit trivial, une fois que vous ajoutez des hiérarchies de classes, des méthodes virtuelles, des résumés et des interfaces, d'autres surcharges et, parfois plusieurs héritages, il devient très complexe très rapidement de décider quelle méthode doit être résolue. C'est un choix des concepteurs de ne pas suivre cette voie, mais d'autres langues ont décidé différemment à différents niveaux de succès.
Abel

-2

La plupart des langages statiques prennent également en charge les génériques, ce qui résoudrait votre problème. Comme indiqué précédemment, sans avoir de différences de paramètres, il n'y a aucun moyen de savoir lequel appeler. Donc, si vous voulez le faire, utilisez simplement des génériques et appelez-le un jour.


Pas la même chose. Comment géreriez-vous une fonction qui traduit une entrée en un entier, un flottant, un booléen ou autre en fonction de la façon dont le type de retour est utilisé? Il ne peut pas être généralisé car vous avez besoin d'un cas spécial pour chacun.
Jay Conrod

Voir codeproject.com/KB/cpp/returnoverload.aspx pour une stratégie intelligente de "surcharge sur le type de retour". Fondamentalement, au lieu de définir une fonction func (), vous définissez une structure func, lui donnez un opérateur () () et des conversions à chaque type approprié.
j_random_hacker

Jay, vous définissez le type de retour lorsque vous appelez la fonction. Si les inpus sont différents, alors il n'y a aucun problème. S'il en est de même, vous pouvez avoir une version générique qui peut avoir une logique basée sur le type utilisant GetType ().
Charles Graham
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.