¿Sobrecarga de funciones por tipo de retorno?


252

¿Por qué los lenguajes de tipo estático más convencionales no admiten la sobrecarga de funciones / métodos por tipo de retorno? No se me ocurre ninguno que lo haga. Parece no menos útil o razonable que soportar la sobrecarga por tipo de parámetro. ¿Cómo es que es mucho menos popular?


Respuestas:


523

Al contrario de lo que otros dicen, la sobrecarga por tipo de retorno es posible y se realiza en algunos idiomas modernos. La objeción habitual es que en código como

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

No se puede saber cuál func()se llama. Esto se puede resolver de varias maneras:

  1. Tenga un método predecible para determinar qué función se llama en tal situación.
  2. Cada vez que ocurre una situación así, es un error en tiempo de compilación. Sin embargo, tiene una sintaxis que permite al programador desambiguar, por ejemplo int main() { (string)func(); }.
  3. No tiene efectos secundarios. Si no tiene efectos secundarios y nunca usa el valor de retorno de una función, entonces el compilador puede evitar llamar a la función en primer lugar.

Dos de los idiomas que uso regularmente ( ab ) usan sobrecarga por tipo de retorno: Perl y Haskell . Déjame describir lo que hacen.

En Perl , hay una distinción fundamental entre el contexto escalar y de lista (y otros, pero pretendemos que hay dos). Cada función incorporada en Perl puede hacer cosas diferentes dependiendo del contexto en el que se llama. Por ejemplo, el joinoperador fuerza el contexto de la lista (en la cosa que se está uniendo) mientras que el scalaroperador fuerza el contexto escalar, así que 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.

Cada operador en Perl hace algo en contexto escalar y algo en contexto de lista, y pueden ser diferentes, como se ilustra. (Esto no es solo para operadores aleatorios como localtime. Si usa una matriz @aen el contexto de la lista, devuelve la matriz, mientras que en el contexto escalar, devuelve el número de elementos. Entonces, por ejemplo, print @aimprime los elementos, mientras print 0+@aimprime el tamaño. ) Además, cada operador puede forzar un contexto, por ejemplo, la suma +fuerza el contexto escalar. Cada entrada en los man perlfuncdocumentos esto. Por ejemplo, aquí hay parte de la entrada para glob EXPR:

En el contexto de la lista, devuelve una lista (posiblemente vacía) de expansiones de nombre de archivo en el valor de lo que haría el EXPRshell estándar de Unix /bin/csh. En contexto escalar, glob itera a través de tales expansiones de nombre de archivo, devolviendo undef cuando la lista se agota.

Ahora, ¿cuál es la relación entre la lista y el contexto escalar? Bueno man perlfuncdice

Recuerde la siguiente regla importante: no existe una regla que relacione el comportamiento de una expresión en el contexto de la lista con su comportamiento en el contexto escalar, o viceversa. Podría hacer dos cosas totalmente diferentes. Cada operador y función decide qué tipo de valor sería más apropiado devolver en contexto escalar. Algunos operadores devuelven la longitud de la lista que habría sido devuelta en el contexto de la lista. Algunos operadores devuelven el primer valor en la lista. Algunos operadores devuelven el último valor en la lista. Algunos operadores devuelven un recuento de operaciones exitosas. En general, hacen lo que quieres, a menos que quieras consistencia.

así que no se trata simplemente de tener una sola función, y luego se realiza una conversión simple al final. De hecho, elegí el localtimeejemplo por ese motivo.

No son solo los elementos integrados los que tienen este comportamiento. Cualquier usuario puede definir dicha función utilizando wantarray, lo que le permite distinguir entre lista, escalar y contexto vacío. Entonces, por ejemplo, puedes decidir no hacer nada si te llaman en un contexto vacío.

Ahora, puede quejarse de que esto no es una sobrecarga verdadera por valor de retorno porque solo tiene una función, que se le dice al contexto en el que se invoca y luego actúa sobre esa información. Sin embargo, esto es claramente equivalente (y análogo a cómo Perl no permite la sobrecarga habitual literalmente, pero una función solo puede examinar sus argumentos). Además, resuelve muy bien la ambigua situación mencionada al comienzo de esta respuesta. Perl no se queja de que no sabe a qué método llamar; solo lo llama. Todo lo que tiene que hacer es averiguar en qué contexto se llamó a la función, que siempre es posible:

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 veces puedo decir operador Perl cuando me refiero a la función. Esto no es crucial para esta discusión).

Haskell adopta el otro enfoque, es decir, no tener efectos secundarios. También tiene un sistema de tipo fuerte, por lo que puede escribir código como el siguiente:

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

Este código lee un número de coma flotante de la entrada estándar e imprime su raíz cuadrada. Pero, ¿qué es lo sorprendente de esto? Bueno, el tipo de readLnes readLn :: Read a => IO a. Lo que esto significa es que para cualquier tipo que pueda ser Read(formalmente, cada tipo que sea una instancia de la Readclase de tipo), readLnpuede leerlo. ¿Cómo sabía Haskell que quería leer un número de coma flotante? Bueno, el tipo de sqrtes sqrt :: Floating a => a -> a, lo que esencialmente significa que sqrtsolo puede aceptar números de punto flotante como entradas, por lo que Haskell dedujo lo que quería.

¿Qué sucede cuando Haskell no puede inferir lo que quiero? Bueno, hay algunas posibilidades. Si no uso el valor de retorno, Haskell simplemente no llamará a la función en primer lugar. Sin embargo, si yo hago utilizar el valor de retorno, a continuación, Haskell se quejará de que no se puede inferir el tipo:

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

Puedo resolver la ambigüedad especificando el tipo que quiero:

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

De todos modos, lo que significa toda esta discusión es que la sobrecarga por valor de retorno es posible y se hace, lo que responde parte de su pregunta.

La otra parte de su pregunta es por qué más idiomas no lo hacen. Dejaré que otros respondan eso. Sin embargo, algunos comentarios: la razón principal es probablemente que la oportunidad de confusión es realmente mayor aquí que en la sobrecarga por tipo de argumento. También puede ver los fundamentos de idiomas individuales:

Ada : "Puede parecer que la regla de resolución de sobrecarga más simple es usar todo, toda la información de un contexto lo más amplio posible, para resolver la referencia sobrecargada. Esta regla puede ser simple, pero no es útil. Requiere el lector humano para escanear fragmentos de texto arbitrariamente grandes y para hacer inferencias arbitrariamente complejas (como (g) arriba). Creemos que una mejor regla es aquella que hace explícita la tarea que debe realizar un lector humano o un compilador, y eso hace que esta tarea tan natural para el lector humano como sea posible ".

C ++ (subsección 7.4.1 de "El lenguaje de programación C ++" de Bjarne Stroustrup): "Los tipos de retorno no se consideran en la resolución de sobrecarga. El motivo es mantener la resolución de un operador individual o llamada de función independiente del 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)
}

Si se tuviera en cuenta el tipo de retorno, ya no sería posible mirar una llamada de forma sqrt()aislada y determinar qué función se llamó ". (Tenga en cuenta, para comparar, que en Haskell no hay conversiones implícitas ).

Java ( Java Language Specification 9.4.1 ): "Uno de los métodos heredados debe ser sustituible por el tipo de retorno para cualquier otro método heredado, de lo contrario se produce un error en tiempo de compilación". (Sí, sé que esto no da una razón. Estoy seguro de que la razón es dada por Gosling en "el lenguaje de programación Java". ¿Quizás alguien tiene una copia? Apuesto a que es el "principio de la menor sorpresa" en esencia. ) Sin embargo, un dato curioso sobre Java: ¡la JVM permite la sobrecarga por valor de retorno! Esto se usa, por ejemplo, en Scala , y también se puede acceder directamente a través de Java jugando con elementos internos.

PD. Como nota final, en realidad es posible sobrecargar por valor de retorno en C ++ con un truco. Testigo:

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
}

Gran publicación, pero es posible que desee aclarar qué es la lectura (Cadena -> algo).
Thomas Eding

C ++ también le permite sobrecargar por valor devuelto const / not const. stackoverflow.com/questions/251159/…
geon

3
Para su último truco con la sobrecarga de los operadores de coerción, la línea "cout" funciona a veces, pero casi cualquier cambio que realice en el código hace que produzca una "sobrecarga ambigua para 'operador <<'".
Steve

1
El enfoque que preferiría sería requerir que una sobrecarga se marque como "preferida"; el compilador comenzaría enlazando usando solo sobrecargas preferidas, y luego determinaría si alguna sobrecarga no preferida sería una mejora. Entre otras cosas, suponga tipos Fooy Barsoporte de conversión bidireccional, y un método usa el tipo Foointernamente pero devuelve el tipo Bar. Si se llama a dicho método mediante un código que obligará de inmediato a escribir el resultado Foo, usar el Bartipo de retorno podría funcionar, pero Foosería mejor. Por cierto, también me gustaría ver un medio por el cual ...
supercat

... un método podría designar qué tipo debería usarse en una construcción como var someVar = someMethod();(o de lo contrario designar que su retorno no debería usarse de esa manera). Por ejemplo, una familia de tipos que implementa una interfaz Fluent podría beneficiarse de tener versiones mutables e inmutables, por var thing2 = thing1.WithX(3).WithY(5).WithZ(9);lo que tendría que WithX(3)copiar thing1a un objeto mutable, mutar X y devolver ese objeto mutable; WithY(5)mutaría Y y devolvería el mismo objeto; asimismo `WithZ (9). Entonces la asignación se convertiría en un tipo inmutable.
supercat

37

Si el tipo de retorno sobrecargó las funciones y usted tuvo estas dos sobrecargas

int func();
string func();

no hay forma de que el compilador pueda determinar a cuál de esas dos funciones llamar al ver una llamada como esta

void main() 
{
    func();
}

Por esta razón, los diseñadores de idiomas a menudo no permiten la sobrecarga del valor de retorno.

Algunos idiomas (como MSIL), sin embargo, no permiten la sobrecarga según el tipo de retorno. También se enfrentan a la dificultad anterior, por supuesto, pero tienen soluciones alternativas, por lo que deberá consultar su documentación.


44
Una pequeña objeción (su respuesta da una razón muy clara y comprensible): no es que no haya forma; es solo que las formas serían torpes y más dolorosas de lo que a la mayoría de la gente le gustaría. Por ejemplo, en C ++, la sobrecarga probablemente podría resolverse utilizando una sintaxis de conversión fea.
Michael Burr el

2
@ Jörg W Mittag: No ves lo que hacen las funciones. Fácilmente podrían tener diferentes efectos secundarios.
A. Rex el

2
@ Jörg: en la mayoría de los lenguajes de programación convencionales (C / C ++, C #, Java, etc.), las funciones suelen tener efectos secundarios. De hecho, supongo que las funciones con efectos secundarios son al menos tan comunes como las que no.
Michael Burr el

66
Saltar tarde aquí, pero en algunos contextos "función" tiene la definición limitada de (esencialmente) "un método sin efectos secundarios". Más coloquialmente, "función" a menudo se usa indistintamente con "método" o "subrutina". Jorg es riguroso o pedante, dependiendo de su punto de vista :)
AwesomeTown

3
Incluso más tarde, algunos puntos de vista podrían usar adjetivos que no sean rigurosos o pedantes
Patrick McDonald

27

En tal lenguaje, ¿cómo resolvería lo siguiente:

f(g(x))

si ftuvo sobrecargas void f(int)y void f(string)y gtuvo sobrecargas int g(int)y string g(int)? Necesitarías algún tipo de desambigador.

Creo que las situaciones en las que podría necesitar esto se resolverían mejor eligiendo un nuevo nombre para la función.


2
El tipo regular de sobrecarga también puede generar ambigüedades. Creo que normalmente se resuelven contando el número de lanzamientos necesarios, pero esto no siempre funciona.
Jay Conrod

1
Sí, las conversiones estándar se clasifican en concordancia exacta, promoción y conversión: nulo f (int); nulo f (largo); fa'); llama a f (int), porque eso es solo una promoción, mientras que convertir a largo es una conversión. nulo f (flotador); nulo f (corto); f (10); requeriría conversión para ambos: la llamada es ambigua.
Johannes Schaub - litb

Si el lenguaje tiene una evaluación perezosa, esto no es un gran problema.
jdd

Voto a favor, la interacción de la sobrecarga de tipo de parámetro y la sobrecarga de tipo de retorno no se aborda en la publicación de Rex. Muy buen punto.
Joseph Garvin el

1
Si estuviera diseñando un idioma, mi regla sería que para cualquier función sobrecargada, cada firma de parámetro debe tener un tipo de retorno designado como predeterminado; un compilador comenzaría asumiendo que cada llamada a la función usaría el tipo predeterminado. Sin embargo, una vez que se hizo eso, en cada situación en la que el valor de retorno de una función se convirtió inmediatamente o se coaccionó a otra cosa, el compilador verificaría una sobrecarga cuya firma de parámetro sea idéntica, pero cuyo tipo de retorno sea una mejor coincidencia (o posiblemente nula) . Probablemente también impondría una regla de "override-one - override-all" para tales sobrecargas.
supercat

19

Para robar una respuesta específica de C ++ de otra pregunta muy similar (¿engañado?):


Los tipos de retorno de funciones no entran en juego en la resolución de sobrecarga simplemente porque Stroustrup (supongo que con la entrada de otros arquitectos de C ++) quería que la resolución de sobrecarga fuera 'independiente del contexto'. Consulte 7.4.1 - "Tipo de sobrecarga y retorno" del "Lenguaje de programación C ++, tercera edición".

La razón es mantener la resolución de un operador individual o llamada de función independiente del contexto.

Querían que se basara solo en cómo se llamaba la sobrecarga, no en cómo se usaba el resultado (si se usaba). De hecho, se llaman muchas funciones sin usar el resultado o el resultado se usaría como parte de una expresión más grande. Un factor que estoy seguro entró en juego cuando decidieron que esto era que si el tipo de retorno era parte de la resolución, habría muchas llamadas a funciones sobrecargadas que tendrían que resolverse con reglas complejas o tendrían que hacer que el compilador arroje Un error de que la llamada era ambigua.

Y, Dios sabe, la resolución de sobrecarga de C ++ es lo suficientemente compleja como está ...


5

En Haskell es posible aunque no tenga sobrecarga de funciones. Haskell usa clases de tipo. En un programa puedes 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

La sobrecarga de funciones en sí no es tan popular. La mayoría de los lenguajes que he visto con él son C ++, quizás java y / o C #. En todos los lenguajes dinámicos es una abreviatura 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()

Por lo tanto, no tiene mucho sentido. A la mayoría de las personas no les interesa si el lenguaje puede ayudarlo a soltar una sola línea por donde lo use.

La coincidencia de patrones es algo similar a la sobrecarga de funciones, y supongo que a veces funcionan de manera similar. Sin embargo, no es común porque es útil solo para algunos programas y es difícil de implementar en la mayoría de los idiomas.

Verá que hay infinitas funciones más fáciles de implementar para implementar en el lenguaje, que incluyen:

  • Escritura dinámica
  • Soporte interno para listas, diccionarios y cadenas unicode
  • Optimizaciones (JIT, inferencia de tipos, compilación)
  • Herramientas integradas de implementación
  • Soporte de la biblioteca
  • Apoyo comunitario y lugares de reunión.
  • Ricas bibliotecas estándar
  • Buena sintaxis
  • Leer eval print loop
  • Soporte para programación reflexiva.

3
Haskell tiene sobrecarga. Las clases de tipos son la característica del lenguaje que se utiliza para definir funciones sobrecargadas.
Lii

2

Buenas respuestas! La respuesta de A.Rex en particular es muy detallada e instructiva. Como él señala, C ++ no consideran operadores tipo de conversión suministrados por el usuario al compilar lhs = func(); (donde func es realmente el nombre de una estructura) . Mi solución es un poco diferente, no mejor, solo diferente (aunque se basa en la misma idea básica).

Mientras que yo quería escribir ...

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

Terminé con una solución que usa una estructura parametrizada (con T = el 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 :)

Una ventaja de esta solución es que cualquier código que incluya estas definiciones de plantilla puede agregar más especializaciones para más tipos. También puede hacer especializaciones parciales de la estructura según sea necesario. Por ejemplo, si desea un manejo especial para los tipos de puntero:

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

Como negativo, no puedes escribir int x = func();con mi solución. Tienes que escribir int x = func<int>();. Debe decir explícitamente cuál es el tipo de retorno, en lugar de pedirle al compilador que lo analice mirando los operadores de conversión de tipo. Diría que "mi" solución y las de A.Rex pertenecen a un frente pareto-óptimo de formas de abordar este dilema de C ++ :)


1

si desea sobrecargar los métodos con diferentes tipos de retorno, simplemente agregue un parámetro ficticio con el valor predeterminado para permitir la ejecución de la sobrecarga, pero no olvide que el tipo de parámetro debe ser diferente para que la lógica de sobrecarga funcione a continuación, por ejemplo, en delphi:

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

úsalo así

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;

Esa es una idea terrible. No introduzca parámetros ficticios, es un gran olor a código. En su lugar, elija diferentes nombres, o elija un tipo de retorno que pueda actuar, o sea una unión discriminada o algo así.
Abel

@Abel lo que estás sugiriendo es en realidad la idea terrible, porque toda la idea se trata de este parámetro ficticio, y se llama así para dejar en claro para el desarrollador que este parámetro es ficticio y debe ignorarse, también en caso de que no sé, los parámetros ficticios con valores predeterminados se usan en muchas bibliotecas, VCL en delphi y muchos IDEs, por ejemplo, en delphi se puede ver en la unidad sysutils en SafeLoadLibrary ...
ZORRO_BLANCO

Ciertamente, hay escenarios en los que los parámetros ficticios son útiles, como en lambdas en operaciones de mapa o plegado, o al implementar una interfaz. Pero por el simple hecho de crear una sobrecarga, no, ruego no estar de acuerdo. No es necesario y es un ruido sin el que los programadores pueden vivir.
Abel

0

Como ya se mostró, las llamadas ambiguas de una función que difiere solo por el tipo de retorno introducen ambigüedad. La ambigüedad induce código defectuoso. El código defectuoso debe ser evitado.

La complejidad impulsada por el intento de ambigüedad muestra que este no es un buen truco. Además de un ejercicio intelectual, ¿por qué no utilizar procedimientos con parámetros de referencia?

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

Porque no puede reutilizar fácilmente los valores de "retorno" de inmediato. Tendría que hacer cada llamada en una sola línea, en lugar dedoing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
Adowrath

0

Esta característica de sobrecarga no es difícil de administrar, si la miras de una manera ligeramente diferente. considera lo siguiente,

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

si un idioma devolviera la sobrecarga, permitiría la sobrecarga de parámetros, pero no las duplicaciones. Esto resolvería el problema de:

main (){
f(x)
}

porque solo hay una f (int elección) para elegir.


0

En .NET, a veces usamos un parámetro para indicar la salida deseada de un resultado genérico, y luego realizamos una conversión para obtener lo 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();
    }

Quizás este ejemplo también podría ayudar:

C ++

    #include <iostream>

enum class FooReturnType{ //Only C++11
    IntType,
    StringType,
    WeaType
}_FooReturnType;

class Wea{
public:
    const char* ToString(){
        return "Wea class";
    }
};

void* Foo(FooReturnType type){
    void* result = 0;
    if (type == FooReturnType::IntType) //Only C++11
    {
        /*Int related actions*/
        result = (void*)1;
    }
    else if (type == FooReturnType::StringType) //Only C++11
    {
        /*String related actions*/
        result = (void*)"Some important text";
    }
    else if (type == FooReturnType::WeaType) //Only C++11
    {
        /*Wea related actions*/
        result = (void*)new Wea();
    }
    return result;
}

int main(int argc, char* argv[])
{
    int intReturn = (int)Foo(FooReturnType::IntType);
    const char* stringReturn = (const char*)Foo(FooReturnType::StringType);
    Wea *someWea = static_cast<Wea*>(Foo(FooReturnType::WeaType));
    std::cout << "Expecting Int from Foo: " << intReturn << std::endl;
    std::cout << "Expecting String from Foo: " << stringReturn << std::endl;
    std::cout << "Expecting Wea from Foo: " << someWea->ToString() << std::endl;
    delete someWea; // Don't leak oil!
    return 0;
}

1
Es un poco agresivo y podría generar errores en tiempo de ejecución si el usuario no emite correctamente el resultado o si el desarrollador no hace coincidir correctamente los tipos de retorno con la enumeración. Recomendaría usar un enfoque basado en plantillas (o parámetros genéricos en C #?) Como en esta respuesta
sleblanc

0

Para el registro, Octave permite un resultado diferente según el elemento de retorno es escalar frente a 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 también Descomposición del valor singular .


0

Este es ligeramente diferente para C ++; No sé si se consideraría una sobrecarga por tipo de retorno directamente. Es más una plantilla de especialización que actúa de la misma manera.

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 ejemplo no utiliza exactamente la resolución de sobrecarga de funciones por tipo de retorno, sin embargo, esta clase de no objeto de C ++ está utilizando la especialización de plantilla para simular la resolución de sobrecarga de funciones por tipo de retorno con un método estático privado.

Cada una de las convertToTypefunciones llama a la plantilla de función stringToValue()y si observa los detalles de implementación o el algoritmo de esta plantilla de función, llama getValue<T>( param, param )y devuelve un tipo Ty lo almacena en uno T*que se pasa a la stringToValue()plantilla de función como uno de sus parámetros .

Aparte de algo como esto; C ++ realmente no tiene un mecanismo para tener una resolución de sobrecarga de funciones por tipo de retorno. Puede haber otras construcciones o mecanismos que no conozco que podrían simular la resolución por tipo de retorno.


-1

Creo que esto es un GAP en la definición 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 qué un compilador de C ++ no puede arrojar un error en el ejemplo "3" y aceptar el código en el ejemplo "1 + 2"?


Sí, eso es lo que han estado considerando en ese momento para C # (y tal vez C ++). Pero si bien su código es trivial, una vez que agrega jerarquías de clases, métodos virtuales, resúmenes e interfaces, otras sobrecargas y, a veces, herencia múltiple, se vuelve muy complejo muy rápidamente decidir qué método debe resolverse. Es una elección de los diseñadores no seguir ese camino, pero otros idiomas han decidido de manera diferente en varios niveles de éxito.
Abel

-2

La mayoría de los lenguajes estáticos ahora también admiten genéricos, lo que resolvería su problema. Como se indicó anteriormente, sin tener diferencias de parámetros, no hay forma de saber a cuál llamar. Entonces, si quieres hacer esto, solo usa genéricos y llámalo por día.


No es lo mismo ¿Cómo manejaría una función que traduce la entrada a un número entero, flotante, bool o lo que sea en función de cómo se usa el tipo de retorno? No se puede generalizar ya que necesita un caso especial para cada uno.
Jay Conrod

Consulte codeproject.com/KB/cpp/returnoverload.aspx para obtener una estrategia inteligente para la "sobrecarga en el tipo de retorno". Básicamente, en lugar de definir una función func (), usted define una estructura func, le da un operador () () y conversiones a cada tipo apropiado.
j_random_hacker

Jay, usted define el tipo de retorno cuando llama a la función. Si los inpus son diferentes, entonces no hay ningún problema. Si hay lo mismo, puede tener una versión genérica que puede tener cierta lógica basada en el tipo que usa GetType ().
Charles Graham el
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.