Sobrecarga de função por tipo de retorno?


252

Por que mais idiomas tipicamente estatísticos não suportam sobrecarga de função / método por tipo de retorno? Não consigo pensar em nada disso. Parece não menos útil ou razoável do que suportar sobrecarga por tipo de parâmetro. Como é que é tão menos popular?


Respostas:


523

Ao contrário do que os outros estão dizendo, a sobrecarga por tipo de retorno é possível e é feita por alguns idiomas modernos. A objeção usual é que em código como

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

você não pode dizer qual func()está sendo chamado. Isso pode ser resolvido de algumas maneiras:

  1. Tenha um método previsível para determinar qual função é chamada nessa situação.
  2. Sempre que essa situação ocorre, é um erro em tempo de compilação. No entanto, tenha uma sintaxe que permita ao programador desambiguar, por exemplo int main() { (string)func(); }.
  3. Não tem efeitos colaterais. Se você não tiver efeitos colaterais e nunca usar o valor de retorno de uma função, o compilador poderá evitar chamar a função em primeiro lugar.

Dois dos idiomas que eu regularmente ( ab ) usam sobrecarga por tipo de retorno: Perl e Haskell . Deixe-me descrever o que eles fazem.

No Perl , existe uma distinção fundamental entre o contexto escalar e a lista (e outros, mas vamos fingir que existem dois). Toda função interna do Perl pode fazer coisas diferentes, dependendo do contexto em que é chamada. Por exemplo, o joinoperador força o contexto da lista (na coisa que está sendo unida) enquanto o scalaroperador força o contexto escalar, então compare:

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.

Todo operador no Perl faz algo no contexto escalar e algo no contexto da lista, e eles podem ser diferentes, como ilustrado. (Isso não é apenas para operadores aleatórios, como localtime. Se você usa uma matriz @ano contexto de lista, ela retorna a matriz, enquanto no contexto escalar, retorna o número de elementos. Por exemplo, print @aimprime os elementos, enquanto print 0+@aimprime o tamanho. ) Além disso, todo operador pode forçar um contexto, por exemplo, a adição +força o contexto escalar. Toda entrada em man perlfuncdocumentos isso. Por exemplo, aqui faz parte da entrada para glob EXPR:

No contexto de lista, retorna uma lista (possivelmente vazia) de expansões de nome de arquivo com o valor EXPRque o shell Unix padrão /bin/cshfaria. No contexto escalar, o glob percorre essas expansões de nome de arquivo, retornando undef quando a lista estiver esgotada.

Agora, qual é a relação entre lista e contexto escalar? Bem man perlfuncdiz

Lembre-se da seguinte regra importante: Não existe uma regra que relacione o comportamento de uma expressão no contexto de lista com seu comportamento no contexto escalar ou vice-versa. Pode fazer duas coisas totalmente diferentes. Cada operador e função decide que tipo de valor seria mais apropriado retornar no contexto escalar. Alguns operadores retornam o comprimento da lista que seria retornada no contexto da lista. Alguns operadores retornam o primeiro valor na lista. Alguns operadores retornam o último valor na lista. Alguns operadores retornam uma contagem de operações bem-sucedidas. Em geral, eles fazem o que você deseja, a menos que você queira consistência.

portanto, não é uma simples questão de ter uma única função, e você faz uma conversão simples no final. De fato, escolhi o localtimeexemplo por esse motivo.

Não são apenas os integrados que têm esse comportamento. Qualquer usuário pode definir essa função usando wantarray, o que permite distinguir entre lista, escalar e contexto nulo. Assim, por exemplo, você pode decidir não fazer nada se estiver sendo chamado em um contexto nulo.

Agora, você pode reclamar que isso não é uma sobrecarga verdadeira por valor de retorno, porque você tem apenas uma função, a qual é informada no contexto em que é chamada e, em seguida, age sobre essas informações. No entanto, isso é claramente equivalente (e análogo a como o Perl não permite literalmente a sobrecarga usual, mas uma função pode apenas examinar seus argumentos). Além disso, resolve bem a situação ambígua mencionada no início desta resposta. Perl não reclama que não sabe qual método chamar; apenas chama isso. Tudo o que precisa fazer é descobrir em que contexto a função foi chamada, o que é sempre possível:

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: às vezes posso dizer operador Perl quando quero dizer função. Isso não é crucial para esta discussão.)

Haskell adota a outra abordagem, a saber, para não ter efeitos colaterais. Ele também possui um sistema de tipos robusto e, portanto, você pode escrever um código como o seguinte:

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

Esse código lê um número de ponto flutuante da entrada padrão e imprime sua raiz quadrada. Mas o que é surpreendente nisso? Bem, o tipo de readLné readLn :: Read a => IO a. O que isso significa é que, para qualquer tipo que possa ser Read(formalmente, todo tipo que seja uma instância da Readclasse type), readLnpode lê-lo. Como Haskell sabia que eu queria ler um número de ponto flutuante? Bem, o tipo de sqrté sqrt :: Floating a => a -> a, o que significa essencialmente que sqrtsó pode aceitar números de ponto flutuante como entradas e, portanto, Haskell inferiu o que eu queria.

O que acontece quando Haskell não pode inferir o que eu quero? Bem, existem algumas possibilidades. Se eu não usar o valor de retorno, Haskell simplesmente não chamará a função em primeiro lugar. No entanto, se eu fazer usar o valor de retorno, então Haskell vai reclamar que não pode inferir o tipo:

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

Eu posso resolver a ambiguidade especificando o tipo que eu quero:

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

De qualquer forma, o que toda essa discussão significa é que a sobrecarga pelo valor de retorno é possível e está concluída, o que responde a parte da sua pergunta.

A outra parte da sua pergunta é por que mais idiomas não fazem isso. Vou deixar que outros respondam isso. No entanto, alguns comentários: a principal razão é provavelmente que a oportunidade de confusão é realmente maior aqui do que a sobrecarga por tipo de argumento. Você também pode examinar as justificativas de idiomas individuais:

Ada : "Pode parecer que a regra mais simples de resolução de sobrecarga é usar tudo - todas as informações de um contexto tão amplo quanto possível - para resolver a referência sobrecarregada. Essa regra pode ser simples, mas não é útil. Requer o leitor humano digitalizar pedaços de texto arbitrariamente grandes e fazer inferências arbitrariamente complexas (como (g) acima). Acreditamos que uma regra melhor é aquela que torna explícita a tarefa que um leitor ou compilador humano deve executar e que a torna o mais natural possível para o leitor humano ".

C ++ (subseção 7.4.1 da "Linguagem de programação C ++" de Bjarne Stroustrup): "Os tipos de retorno não são considerados na resolução de sobrecarga. O motivo é manter a resolução para um operador individual ou uma chamada de função independente do contexto. Considere:

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 o tipo de retorno fosse levado em consideração, não seria mais possível examinar uma chamada sqrt()isoladamente e determinar qual função foi chamada. "(Observe, para comparação, que em Haskell não há conversões implícitas ).

Java ( Java Language Specification 9.4.1 ): "Um dos métodos herdados deve ser substituível pelo tipo de retorno para todos os outros métodos herdados; caso contrário, ocorrerá um erro em tempo de compilação." (Sim, eu sei que isso não dá uma justificativa. Tenho certeza de que Gosling é dada na "linguagem de programação Java". Talvez alguém tenha uma cópia? Aposto que é o "princípio da menor surpresa" em essência. ) No entanto, um fato curioso sobre Java: a JVM permite sobrecarga por valor de retorno! Isso é usado, por exemplo, no Scala , e também pode ser acessado diretamente por Java , brincando com os internos.

PS. Como nota final, é realmente possível sobrecarregar pelo valor de retorno em C ++ com um truque. Testemunha:

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
}

Ótimo post, mas você pode esclarecer o que é a leitura (String -> alguma coisa).
Thomas Eding

C ++ também permite sobrecarregar por const / not const retornado valor. stackoverflow.com/questions/251159/…
geon 30/10/10

3
Para o seu último truque com a sobrecarga dos operadores de coerção, a linha "cout" funciona algumas vezes, mas quase todas as alterações que faço no código fazem com que "sobrecarga ambígua para 'operator <<'".
Steve

1
A abordagem que eu preferiria seria exigir que uma sobrecarga fosse marcada como "preferida"; o compilador começaria vinculando usando apenas sobrecargas preferenciais e, em seguida, determinaria se alguma sobrecarga não preferencial seria uma melhoria. Entre outras coisas, tipos suponha Fooe Barconversão bidirecional suporte e um método usa o tipo Foointernamente, mas tipo de retornos Bar. Se esse método for chamado pelo código que forçará imediatamente o resultado a ser digitado Foo, o uso do Bartipo de retorno poderá funcionar, mas o Foomelhor seria. BTW, eu também gostaria de ver um meio através do qual ... #
3013

... um método pode designar que tipo deve ser usado em uma construção como var someVar = someMethod();(ou então designar que seu retorno não deve ser usado dessa maneira). Por exemplo, uma família de tipos que implementa uma interface Fluent pode se beneficiar de ter versões mutáveis ​​e imutáveis, para var thing2 = thing1.WithX(3).WithY(5).WithZ(9);que WithX(3)copiem thing1para um objeto mutável, mutem X e retornem esse objeto mutável; WithY(5)mudaria Y e retornaria o mesmo objeto; da mesma forma `WithZ (9). Em seguida, a atribuição seria convertida em um tipo imutável.
Supercat

37

Se as funções foram sobrecarregadas pelo tipo de retorno e você teve essas duas sobrecargas

int func();
string func();

não há como o compilador descobrir qual dessas duas funções chamar ao ver uma chamada como esta

void main() 
{
    func();
}

Por esse motivo, os designers de idiomas geralmente não permitem a sobrecarga de valor de retorno.

Algumas linguagens (como MSIL), no entanto, não permitem a sobrecarga por tipo de retorno. Eles também enfrentam a dificuldade acima, é claro, mas têm soluções alternativas, para as quais você terá que consultar a documentação deles.


4
Uma queixa menor (sua resposta dá uma lógica muito clara e compreensível): não é que não tenha jeito; é que os caminhos seriam desajeitados e mais dolorosos do que a maioria das pessoas gostaria. Por exemplo, em C ++, a sobrecarga provavelmente teria sido resolvida usando uma sintaxe de conversão feia.
Michael Burr

2
@ Jörg W Mittag: Você não vê o que as funções fazem. Eles poderiam facilmente ter efeitos colaterais diferentes .
A.Rex

2
@ Jörg - na maioria das linguagens de programação convencionais (C / C ++, C #, Java, etc.), as funções geralmente têm efeitos colaterais. Na verdade, eu acho que funções com efeitos colaterais são pelo menos tão comuns quanto aquelas sem.
Michael Burr

6
Pulando aqui tarde, mas em alguns contextos "função" tem a definição estreita (essencialmente) de "um método sem efeitos colaterais". Mais coloquialmente, "função" é freqüentemente usada de forma intercambiável com "método" ou "sub-rotina". Jorg ou é ser rigoroso ou pedante, dependendo do seu ponto de vista :)
Awesometown

3
Saltando no mesmo mais tarde, alguns pontos de vista pode usar outros adjetivos que rigorosa ou pedante
Patrick McDonald

27

Nesse idioma, como você resolveria o seguinte:

f(g(x))

se ftinha sobrecargas void f(int)e void f(string)e gteve sobrecargas int g(int)e string g(int)? Você precisaria de algum tipo de desambiguação.

Eu acho que as situações em que você pode precisar disso seriam melhor atendidas escolhendo um novo nome para a função.


2
O tipo regular de sobrecarga também pode resultar em ambiguidades. Penso que estes são normalmente resolvidos contando o número de transmissões necessárias, mas isso nem sempre funciona.
Jay Conrod

1
sim, as conversões padrão são classificadas em correspondência exata, promoção e conversão: void f (int); n vazio f (longo); f ('a'); chama f (int), porque isso é apenas uma promoção, enquanto a conversão para longa é uma conversão. void f (float); n vazio f (curto); f (10); exigiria conversão para ambos: a chamada é ambígua.
Johannes Schaub - litb 14/01

Se o idioma tiver uma avaliação lenta, isso não será um problema.
Jdd 30/10

Upvote, a interação de sobrecarga de tipo de parâmetro e sobrecarga de tipo de retorno não é abordada na postagem de Rex. Muito bom ponto.
Joseph Garvin

1
Se eu estivesse projetando um idioma, minha regra seria que, para qualquer função sobrecarregada, cada assinatura de parâmetro tenha um tipo de retorno designado como padrão; um compilador começaria assumindo que cada chamada de função usaria o tipo padrão. Uma vez feito isso, no entanto, em todas as situações em que o valor de retorno de uma função foi imediatamente convertido ou coagido para outra coisa, o compilador verificaria uma sobrecarga cuja assinatura de parâmetro é idêntica, mas cujo tipo de retorno é uma melhor correspondência (ou possivelmente nula) . Eu provavelmente também imporia uma regra de "substituir um - substituir todos" para essas sobrecargas.
Super12

19

Para roubar uma resposta específica do C ++ de outra pergunta muito semelhante (dupe?):


Os tipos de retorno de função não entram em jogo na resolução de sobrecarga simplesmente porque Stroustrup (suponho que com a entrada de outros arquitetos C ++) queria que a resolução de sobrecarga fosse 'independente do contexto'. Veja 7.4.1 - "Tipo de Sobrecarga e Retorno" da "Linguagem de Programação C ++, Terceira Edição".

O motivo é manter a resolução de um operador individual ou de uma chamada de função independente do contexto.

Eles queriam que se baseasse apenas em como a sobrecarga foi chamada - não em como o resultado foi usado (se foi usado). De fato, muitas funções são chamadas sem usar o resultado ou o resultado seria usado como parte de uma expressão maior. Um fator que tenho certeza de que entrou em jogo quando eles decidiram isso foi que, se o tipo de retorno fizesse parte da resolução, haveria muitas chamadas para funções sobrecarregadas que precisariam ser resolvidas com regras complexas ou teriam que ser executadas pelo compilador um erro que a chamada era ambígua.

E, Deus sabe, a resolução de sobrecarga em C ++ é complexa o suficiente ...


5

No haskell, é possível mesmo que não tenha sobrecarga de função. Haskell usa classes de tipos. Em um programa você pode ver:

class Example a where
    example :: Integer -> a

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

A sobrecarga de funções em si não é tão popular. A maioria das linguagens que já vi são C ++, talvez java e / ou C #. Em todas as linguagens dinâmicas, é uma abreviação para:

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

Portanto, não há muito sentido nisso. A maioria das pessoas não está interessada em saber se o idioma pode ajudá-lo a eliminar uma única linha por onde quer que você o use.

A correspondência de padrões é um pouco semelhante à sobrecarga de funções, e acho que às vezes funciona da mesma forma. Porém, não é comum porque é útil apenas para alguns programas e é difícil de implementar na maioria dos idiomas.

Você vê que existem infinitamente outros recursos melhores e fáceis de implementar para implementar na linguagem, incluindo:

  • Digitação dinâmica
  • Suporte interno para listas, dicionários e strings unicode
  • Otimizações (JIT, inferências de tipo, compilação)
  • Ferramentas de implantação integradas
  • Suporte de biblioteca
  • Apoio comunitário e locais de encontro
  • Bibliotecas padrão ricas
  • Boa sintaxe
  • Ler eval print loop
  • Suporte para programação reflexiva

3
Haskell está sobrecarregado. Classes de tipo é o recurso de idioma usado para definir funções sobrecarregadas.
19412 Lii

2

Boas respostas! A resposta de A.Rex, em particular, é muito detalhada e instrutiva. Como ele aponta, C ++ não considerar operadores de conversão de tipo fornecidos pelo usuário durante a compilação lhs = func(); (onde func é realmente o nome de um struct) . Minha solução alternativa é um pouco diferente - não melhor, apenas diferente (embora seja baseada na mesma idéia básica).

Considerando que eu queria escrever ...

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

Acabei com uma solução que usa uma estrutura parametrizada (com T = o tipo de retorno):

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

Um benefício dessa solução é que qualquer código que inclua essas definições de modelo possa adicionar mais especializações para mais tipos. Além disso, você pode fazer especializações parciais da estrutura, conforme necessário. Por exemplo, se você quisesse tratamento especial para tipos de ponteiros:

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

Como negativo, você não pode escrever int x = func();com a minha solução. Você tem que escrever int x = func<int>();. Você precisa dizer explicitamente qual é o tipo de retorno, em vez de solicitar ao compilador que analise isso examinando os operadores de conversão de tipo. Eu diria que a "minha" solução e a da A.Rex pertencem a uma frente pareto-ideal de maneiras de resolver esse dilema do C ++ :)


1

se você deseja sobrecarregar métodos com diferentes tipos de retorno, basta adicionar um parâmetro fictício com valor padrão para permitir a execução de sobrecarga, mas não esqueça que o tipo de parâmetro deve ser diferente, portanto a lógica de sobrecarga funciona a seguir é um exemplo no delphi:

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

use-o assim

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;

Essa é uma péssima ideia. Não introduza parâmetros fictícios, isso é um grande cheiro de código. Em vez disso, escolha nomes diferentes ou escolha um tipo de retorno que possa agir como uma união discriminada ou algo assim.
Abel

@ Abel, o que você está sugerindo é na verdade a péssima ideia, porque a idéia toda é sobre esse parâmetro fictício e é assim denominada para deixar claro para o desenvolvedor que esse parâmetro é fictício e deve ser ignorado, também no caso de você não sei se os parâmetros fictícios com valores padrão são usados ​​em muitas bibliotecas, VCL no delphi e em muitos IDEs, por exemplo, no delphi, você pode vê-lo na unidade sysutils do SafeLoadLibrary ...
ZORRO_BLANCO

Certamente há cenários em que parâmetros fictícios são úteis, como em lambdas em operações de mapear ou dobrar, ou ao implementar uma interface. Mas, por mero objetivo de criar uma sobrecarga, não, eu imploro discordar. Não há necessidade e é o ruído que os programadores podem viver sem.
Abel

0

Como já mostrado - chamadas ambíguas de uma função que difere apenas pelo tipo de retorno introduz ambiguidade. A ambiguidade induz código defeituoso. Código defeituoso deve ser evitado.

A complexidade impulsionada pela tentativa de ambiguidade mostra que esse não é um bom truque. Além de um exercício intelectual - por que não usar procedimentos com parâmetros de referência.

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

Porque você não pode reutilizar facilmente os valores "return" imediatamente. Você teria que fazer cada chamada em uma única linha, ao contrário dedoing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
Adowrath 18/03/17

0

esse recurso de sobrecarga não é difícil de gerenciar, se você o observar de uma maneira um pouco diferente. considere o seguinte,

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

se um idioma retornasse sobrecarga, isso permitiria sobrecarregar parâmetros, mas não duplicações. isso resolveria o problema de:

main (){
f(x)
}

porque existe apenas um f (int choice) para escolher.


0

No .NET, às vezes usamos um parâmetro para indicar a saída desejada de um resultado genérico e, em seguida, fazemos uma conversão para obter o que esperamos.

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

Talvez este exemplo possa ajudar também:

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
É meio hackeado e pode levar a erros em tempo de execução se o usuário não converter o resultado corretamente ou se o desenvolvedor não corresponder adequadamente os tipos de retorno à enumeração. Eu recomendaria usar uma abordagem baseada em modelo, como em (ou parâmetros genéricos em C #?) Esta resposta
sleblanc

0

Para o registro, o Octave permite resultados diferentes de acordo com o elemento de retorno ser escalar versus matriz.

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

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

Cf também Decomposição de Valor Singular .


0

Este é um pouco diferente para C ++; Não sei se isso seria considerado sobrecarregado por tipo de retorno diretamente. É mais uma especialização de modelo que atua da maneira 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;
}

Este exemplo não está exatamente usando a resolução de sobrecarga de função por tipo de retorno, no entanto, essa classe não-objeto c ++ está usando a especialização de modelo para simular a resolução de sobrecarga de função por tipo de retorno com um método estático privado.

Cada uma das convertToTypefunções está chamando o modelo de função stringToValue()e, se você observar os detalhes da implementação ou o algoritmo desse modelo de função, está chamando getValue<T>( param, param )e retornando um tipo Te armazenando-o em um T*que é passado para o stringToValue()modelo de função como um de seus parâmetros. .

Diferente de algo assim; O C ++ realmente não possui um mecanismo para ter a função sobrecarregando a resolução por tipo de retorno. Pode haver outras construções ou mecanismos que não conheço que possam simular resolução por tipo de retorno.


-1

Eu acho que esse é um GAP na definição moderna de C ++ ... por quê?

int func();
double func();

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

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

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

Por que um compilador C ++ não pode gerar um erro no exemplo "3" e aceitar o código no exemplo "1 + 2"?


Sim, é o que eles estão considerando no momento para C # (e talvez C ++). Porém, embora seu código seja trivial, depois de adicionar hierarquias de classes, métodos virtuais, resumos e interfaces, outras sobrecargas e, às vezes, herança múltipla, fica muito complexo muito rapidamente para decidir qual método deve ser resolvido. É uma escolha dos designers não seguir esse caminho, mas outras línguas decidiram diferentemente em vários níveis de sucesso.
Abel

-2

A maioria das linguagens estáticas agora também oferece suporte a genéricos, o que resolveria seu problema. Como afirmado anteriormente, sem ter diferenças de parâmetro, não há como saber qual chamar. Portanto, se você quiser fazer isso, basta usar genéricos e encerrar o dia.


Não é a mesma coisa. Como você lidaria com uma função que converte a entrada em um número inteiro, float, bool ou qualquer outra coisa com base em como o tipo de retorno é usado? Não pode ser generalizado, pois você precisa de um caso especial para cada um.
Jay Conrod

Consulte codeproject.com/KB/cpp/returnoverload.aspx para obter uma estratégia inteligente para "sobrecarga no tipo de retorno". Basicamente, em vez de definir uma função func (), você define uma struct func, atribui a ela um operador () () e conversões para cada tipo apropriado.
j_random_hacker

Jay, você define o tipo de retorno ao chamar a função. Se as entradas são diferentes, não há problema algum. Se houver o mesmo, você pode ter uma versão genérica que pode ter alguma lógica com base no tipo usando GetType ().
Charles Graham
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.