Fungsi overloading menurut jenis pengembalian?

252

Mengapa bahasa mainstream yang diketik secara statis tidak mendukung kelebihan fungsi / metode dengan jenis kembali? Saya tidak bisa memikirkan yang bisa melakukannya. Tampaknya tidak kurang bermanfaat atau masuk akal daripada mendukung kelebihan berdasarkan tipe parameter. Kenapa itu jauh kurang populer?

dsimcha
sumber

Jawaban:

523

Bertentangan dengan apa yang orang lain katakan, overloading oleh jenis kembali adalah mungkin dan ini dilakukan oleh beberapa bahasa modern. Keberatan yang biasa adalah bahwa dalam kode suka

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

Anda tidak tahu mana func()yang dipanggil. Ini dapat diatasi dengan beberapa cara:

  1. Memiliki metode yang dapat diprediksi untuk menentukan fungsi mana yang dipanggil dalam situasi seperti itu.
  2. Setiap kali situasi seperti itu terjadi, itu adalah kesalahan waktu kompilasi. Namun, memiliki sintaks yang memungkinkan programmer untuk ambigu, misalnyaint main() { (string)func(); } .
  3. Tidak memiliki efek samping. Jika Anda tidak memiliki efek samping dan Anda tidak pernah menggunakan nilai pengembalian suatu fungsi, maka kompiler dapat menghindari pemanggilan fungsi di tempat pertama.

Dua dari bahasa yang saya gunakan ( ab ) secara teratur menggunakan tipe overload: Perl dan Haskell . Biarkan saya jelaskan apa yang mereka lakukan.

Dalam Perl , ada perbedaan mendasar antara skalar dan konteks daftar (dan yang lainnya, tapi kami akan berpura-pura ada dua). Setiap fungsi bawaan di Perl dapat melakukan hal-hal yang berbeda tergantung pada konteksnya . Misalnya, joinoperator memaksa konteks daftar (pada hal yang sedang digabungkan) sementara scalaroperator memaksa konteks skalar, jadi bandingkan:

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.

Setiap operator di Perl melakukan sesuatu dalam konteks skalar dan sesuatu dalam konteks daftar, dan mereka mungkin berbeda, seperti yang diilustrasikan. (Ini bukan hanya untuk operator acak seperti localtime. Jika Anda menggunakan array @adalam konteks daftar, itu akan mengembalikan array, sedangkan dalam konteks skalar, ia mengembalikan jumlah elemen. Jadi misalnya print @amencetak elemen, sambil print 0+@amencetak ukuran. ) Selanjutnya, setiap operator dapat memaksakan suatu konteks, misalnya penambahan +kekuatan konteks skalar. Setiap entri dalam man perlfuncdokumen ini. Misalnya, berikut adalah bagian dari entri untuk glob EXPR:

Dalam konteks daftar, mengembalikan daftar (mungkin kosong) ekspansi nama file pada nilai EXPRseperti yang /bin/cshakan dilakukan shell Unix standar . Dalam konteks skalar, glob iterates melalui ekspansi nama file seperti itu, mengembalikan undef ketika daftar habis.

Sekarang, apa hubungan antara daftar dan konteks skalar? Baiklah, man perlfunckata

Ingat aturan penting berikut: Tidak ada aturan yang menghubungkan perilaku ekspresi dalam konteks daftar dengan perilaku dalam konteks skalar, atau sebaliknya. Mungkin melakukan dua hal yang sama sekali berbeda. Setiap operator dan fungsi memutuskan nilai seperti apa yang paling tepat untuk dikembalikan dalam konteks skalar. Beberapa operator mengembalikan panjang daftar yang akan dikembalikan dalam konteks daftar. Beberapa operator mengembalikan nilai pertama dalam daftar. Beberapa operator mengembalikan nilai terakhir dalam daftar. Beberapa operator mengembalikan hitungan operasi yang berhasil. Secara umum, mereka melakukan apa yang Anda inginkan, kecuali jika Anda menginginkan konsistensi.

jadi itu bukan masalah sederhana memiliki fungsi tunggal, dan kemudian Anda melakukan konversi sederhana pada akhirnya. Sebenarnya, saya memilih localtimecontoh karena alasan itu.

Bukan hanya built-in yang memiliki perilaku ini. Setiap pengguna dapat mendefinisikan fungsi tersebut menggunakan wantarray, yang memungkinkan Anda untuk membedakan antara daftar, skalar, dan konteks batal. Jadi, misalnya, Anda dapat memutuskan untuk tidak melakukan apa pun jika Anda dipanggil dalam konteks kosong.

Sekarang, Anda mungkin mengeluh bahwa ini bukan kelebihan yang sebenarnya dengan nilai pengembalian karena Anda hanya memiliki satu fungsi, yang diberi tahu konteks tempat ia dipanggil dan kemudian bertindak berdasarkan informasi itu. Namun, ini jelas setara (dan analog dengan bagaimana Perl tidak mengizinkan overloading biasa secara harfiah, tetapi suatu fungsi hanya dapat memeriksa argumennya). Selain itu, ini dengan baik menyelesaikan situasi ambigu yang disebutkan di awal respons ini. Perl tidak mengeluh bahwa ia tidak tahu metode panggilan mana; itu hanya menyebutnya. Yang harus dilakukan adalah mencari tahu konteks apa fungsi dipanggil, yang selalu mungkin:

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"

(Catatan: Saya kadang-kadang mengatakan operator Perl ketika maksud saya berfungsi. Ini tidak penting untuk diskusi ini.)

Haskell mengambil pendekatan lain, yaitu tidak memiliki efek samping. Ini juga memiliki sistem tipe yang kuat, sehingga Anda dapat menulis kode seperti berikut:

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

Kode ini membaca angka floating point dari input standar, dan mencetak root kuadratnya. Tapi apa yang mengejutkan tentang ini? Nah, jenisnya readLnadalah readLn :: Read a => IO a. Ini artinya bahwa untuk semua jenis yang dapat Read(secara formal, setiap jenis yang merupakan turunan dari Readkelas jenis), readLndapat membacanya. Bagaimana Haskell tahu bahwa saya ingin membaca angka floating point? Ya, tipe sqrtis sqrt :: Floating a => a -> a, yang pada dasarnya berarti sqrthanya dapat menerima angka floating point sebagai input, dan Haskell menyimpulkan apa yang saya inginkan.

Apa yang terjadi ketika Haskell tidak dapat menyimpulkan apa yang saya inginkan? Nah, ada beberapa kemungkinan. Jika saya tidak menggunakan nilai kembali sama sekali, Haskell tidak akan memanggil fungsi di tempat pertama. Namun, jika saya lakukan menggunakan nilai kembali, maka Haskell akan mengeluh bahwa ia tidak dapat menyimpulkan jenis:

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

Saya dapat mengatasi ambiguitas dengan menentukan jenis yang saya inginkan:

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

Bagaimanapun, apa arti seluruh diskusi ini adalah bahwa kelebihan dengan nilai pengembalian adalah mungkin dan dilakukan, yang menjawab bagian dari pertanyaan Anda.

Bagian lain dari pertanyaan Anda adalah mengapa lebih banyak bahasa tidak melakukannya. Saya akan membiarkan orang lain menjawab itu. Namun, beberapa komentar: alasan prinsipnya mungkin adalah bahwa kesempatan untuk kebingungan benar-benar lebih besar di sini daripada kelebihan beban berdasarkan tipe argumen. Anda juga dapat melihat alasan dari masing-masing bahasa:

Ada : "Mungkin terlihat bahwa aturan resolusi kelebihan beban paling sederhana adalah menggunakan segala sesuatu - semua informasi dari konteks seluas mungkin - untuk menyelesaikan referensi kelebihan beban. Aturan ini mungkin sederhana, tetapi tidak membantu. Ini membutuhkan pembaca manusia untuk memindai teks berukuran besar secara sewenang-wenang, dan untuk membuat kesimpulan kompleks yang sewenang-wenang (seperti (g) di atas). Kami percaya bahwa aturan yang lebih baik adalah aturan yang membuat eksplisit tugas yang harus dilakukan oleh pembaca manusia atau kompiler, dan yang membuat tugas ini sealami mungkin bagi pembaca manusia. "

C ++ (subbab 7.4.1 dari Bjarne Stroustrup "The C ++ Programming Language"): "Jenis kembali tidak dianggap dalam resolusi kelebihan beban. Alasannya adalah untuk menjaga resolusi untuk operator individu atau fungsi panggilan konteks-independen. Pertimbangkan:

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

Jika jenis pengembalian diperhitungkan, tidak mungkin lagi melihat panggilan sqrt()secara terpisah dan menentukan fungsi mana yang dipanggil. "(Perhatikan, sebagai perbandingan, bahwa di Haskell tidak ada konversi implisit .)

Java ( Spesifikasi Bahasa Jawa 9.4.1 ): "Salah satu metode yang diwariskan harus dapat diganti-ketik-kembali untuk setiap metode warisan lainnya, atau kesalahan kompilasi waktu terjadi." (Ya, saya tahu ini tidak memberikan alasan. Saya yakin alasannya diberikan oleh Gosling dalam "Bahasa Pemrograman Java". Mungkin seseorang memiliki salinannya? Saya yakin itu adalah "prinsip kejutan paling tidak" pada dasarnya. ) Namun, fakta menarik tentang Java: JVM memungkinkan kelebihan dengan nilai kembali! Ini digunakan, misalnya, dalam Scala , dan dapat diakses langsung melalui Java juga dengan bermain-main dengan internal.

PS. Sebagai catatan akhir, sebenarnya dimungkinkan untuk membebani dengan mengembalikan nilai dalam C ++ dengan sebuah trik. Saksi:

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
}
A. Rex
sumber
Pos yang bagus, tetapi Anda mungkin ingin memperjelas apakah bacaan itu (String -> something).
Thomas Eding
C ++ juga membuat Anda kelebihan dengan const / bukan nilai yang dikembalikan const. stackoverflow.com/questions/251159/…
geon
3
Untuk trik terakhir Anda dengan overloading operator paksaan, baris "cout" kadang-kadang berfungsi, tetapi hampir semua perubahan yang saya lakukan pada kode membuatnya memberikan "ambigu berlebihan untuk" operator << '".
Steve
1
Pendekatan yang saya sukai adalah mengharuskan satu kelebihan beban ditandai sebagai "lebih disukai"; kompiler akan mulai dengan mengikat hanya menggunakan kelebihan yang disukai, dan kemudian menentukan apakah kelebihan yang tidak disukai akan menjadi perbaikan. Antara lain, anggaplah jenis Foodan Bardukung konversi dua arah, dan metode menggunakan tipe secara Foointernal tetapi tipe pengembalian Bar. Jika metode seperti ini dipanggil oleh kode yang akan segera memaksa hasil untuk mengetik Foo, menggunakan Bartipe pengembalian mungkin berhasil, tetapi yang Fooakan lebih baik. BTW, saya juga ingin melihat cara di mana ...
supercat
... sebuah metode dapat menentukan tipe apa yang harus digunakan dalam konstruksi seperti var someVar = someMethod();(atau yang lain menyatakan bahwa pengembaliannya tidak boleh digunakan dengan cara seperti itu). Misalnya, keluarga jenis yang mengimplementasikan antarmuka Fluent mungkin mendapat manfaat dari memiliki versi yang bisa berubah dan tidak dapat diubah, sehingga var thing2 = thing1.WithX(3).WithY(5).WithZ(9);akan WithX(3)menyalin thing1ke objek yang bisa berubah, bermutasi X, dan mengembalikan objek yang bisa diubah; WithY(5)akan bermutasi Y dan mengembalikan objek yang sama; demikian juga `WithZ (9). Kemudian tugas akan dikonversi ke tipe yang tidak dapat diubah.
supercat
37

Jika fungsi kelebihan beban oleh tipe kembali dan Anda memiliki dua kelebihan ini

int func();
string func();

tidak ada cara kompiler bisa mengetahui mana dari dua fungsi untuk memanggil setelah melihat panggilan seperti ini

void main() 
{
    func();
}

Karena alasan ini, perancang bahasa sering tidak membolehkan kelebihan nilai kembali.

Beberapa bahasa (seperti MSIL), namun, jangan biarkan overloading oleh jenis kembali. Mereka juga menghadapi kesulitan di atas tentu saja, tetapi mereka memiliki solusi, yang Anda harus berkonsultasi dengan dokumentasi mereka.

Frederick The Fool
sumber
4
A minor quibble (jawaban Anda memberikan alasan yang sangat jelas dan dapat dimengerti): bukan berarti tidak ada jalan; hanya saja caranya akan canggung dan lebih menyakitkan daripada yang diinginkan kebanyakan orang. Misalnya, dalam C ++, kelebihan mungkin akan diselesaikan dengan menggunakan beberapa sintaks yang jelek.
Michael Burr
2
@ Jörg W Mittag: Anda tidak melihat fungsi apa yang dilakukan. Mereka dapat dengan mudah memiliki efek samping yang berbeda .
A. Rex
2
@ Jörg - dalam sebagian besar bahasa pemrograman arus utama (C / C ++, C #, Java, dll.) Fungsi umumnya memiliki efek samping. Bahkan, saya kira fungsi dengan efek samping setidaknya sama umum dengan yang tidak.
Michael Burr
6
Melompat terlambat di sini, tetapi dalam beberapa konteks "fungsi" memiliki definisi sempit (dasarnya) "metode tanpa efek samping". Lebih tepatnya, "fungsi" sering digunakan secara bergantian dengan "metode" atau "subrutin". Jorg baik menjadi keras atau bertele-tele, tergantung pada sudut pandang Anda :)
AwesomeTown
3
Melompati bahkan lebih lambat lagi, beberapa sudut pandang mungkin menggunakan kata sifat selain keras atau bertele
Patrick McDonald
27

Dalam bahasa seperti itu, bagaimana Anda menyelesaikan yang berikut:

f(g(x))

jika fmemiliki kelebihan void f(int)dan void f(string)dan gkelebihan int g(int)dan string g(int)? Anda akan membutuhkan semacam disambiguator.

Saya pikir situasi di mana Anda mungkin membutuhkan ini akan lebih baik dilayani dengan memilih nama baru untuk fungsi tersebut.

Greg Hewgill
sumber
2
Jenis overloading reguler dapat menghasilkan ambiguitas juga. Saya pikir ini biasanya diselesaikan dengan menghitung jumlah gips yang diperlukan, tetapi ini tidak selalu berhasil.
Jay Conrod
1
ya, konversi standar digolongkan ke dalam kecocokan, promosi, dan konversi yang tepat: void f (int); batal f (panjang); f ('a'); panggilan f (int), karena itu hanya promosi, sedangkan konversi ke long adalah konversi. batal f (mengambang); batal f (pendek); f (10); akan membutuhkan konversi untuk keduanya: panggilannya tidak jelas.
Johannes Schaub - litb
Jika bahasanya memiliki evaluasi malas, ini bukan masalah besar.
jdd
Upvote, interaksi overloading tipe parameter dan overloading tipe kembali tidak dibahas dalam posting Rex. Poin yang sangat bagus.
Joseph Garvin
1
Jika saya mendesain bahasa, aturan saya adalah bahwa untuk setiap fungsi kelebihan beban, setiap tanda tangan parameter harus memiliki satu tipe pengembalian yang ditetapkan sebagai default; kompiler akan mulai dengan mengasumsikan setiap panggilan fungsi akan menggunakan tipe default. Setelah itu dilakukan, bagaimanapun, dalam setiap situasi di mana nilai kembali fungsi segera dilemparkan atau dipaksa ke sesuatu yang lain, kompiler akan memeriksa kelebihan beban yang tanda tangan parameternya identik, tetapi yang tipe pengembaliannya lebih cocok (atau mungkin batal) . Saya mungkin juga akan memberlakukan aturan "override-one - override-all" untuk overload seperti itu.
supercat
19

Untuk mencuri jawaban khusus C ++ dari pertanyaan lain yang sangat mirip (dupe?):


Jenis fungsi pengembalian tidak ikut bermain dalam resolusi kelebihan hanya karena Stroustrup (saya berasumsi dengan input dari arsitek C ++ lainnya) ingin resolusi kelebihan beban menjadi 'konteks independen'. Lihat 7.4.1 - "Jenis Kelebihan Beban dan Pengembalian" dari "Bahasa Pemrograman C ++, Edisi Ketiga".

Alasannya adalah untuk menjaga resolusi untuk masing-masing operator atau fungsi panggilan konteks-independen.

Mereka ingin itu hanya didasarkan pada bagaimana kelebihan beban itu disebut - bukan bagaimana hasilnya digunakan (jika itu digunakan sama sekali). Memang, banyak fungsi dipanggil tanpa menggunakan hasil atau hasilnya akan digunakan sebagai bagian dari ekspresi yang lebih besar. Salah satu faktor yang saya yakin ikut bermain ketika mereka memutuskan ini adalah bahwa jika tipe kembali adalah bagian dari resolusi akan ada banyak panggilan ke fungsi kelebihan beban yang perlu diselesaikan dengan aturan yang kompleks atau harus memiliki kompilasi lemparan kesalahan bahwa panggilan tidak jelas.

Dan, Tuhan tahu, resolusi kelebihan C ++ cukup kompleks karena berdiri ...

Michael Burr
sumber
5

Di haskell dimungkinkan meskipun tidak memiliki fungsi yang berlebihan. Haskell menggunakan kelas tipe. Dalam sebuah program Anda bisa melihat:

class Example a where
    example :: Integer -> a

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

Fungsi overloading itu sendiri tidak begitu populer. Sebagian besar bahasa yang saya lihat dengan itu adalah C ++, mungkin java dan / atau C #. Dalam semua bahasa dinamis, ini adalah singkatan untuk:

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

Karena itu tidak ada gunanya. Kebanyakan orang tidak tertarik apakah bahasa dapat membantu Anda menjatuhkan satu baris per tempat di mana Anda menggunakannya.

Pencocokan pola agak mirip dengan fungsi overloading, dan saya kira kadang-kadang berfungsi sama. Itu tidak umum karena itu hanya berguna untuk beberapa program dan sulit untuk diterapkan pada sebagian besar bahasa.

Anda lihat ada banyak fitur lain yang lebih mudah diimplementasikan untuk diterapkan ke dalam bahasa, termasuk:

  • Pengetikan dinamis
  • Dukungan internal untuk daftar, kamus, dan string unicode
  • Optimalisasi (JIT, tipe kesimpulan, kompilasi)
  • Alat penyebaran terintegrasi
  • Dukungan perpustakaan
  • Komunitas mendukung dan mengumpulkan tempat
  • Perpustakaan standar kaya
  • Sintaksnya bagus
  • Baca eval print loop
  • Dukungan untuk pemrograman reflektif
Riang
sumber
3
Haskell mengalami overloading. Kelas jenis adalah fitur bahasa yang digunakan untuk mendefinisikan fungsi yang kelebihan beban.
Lii
2

Jawaban yang bagus! Jawaban A.Rex khususnya sangat rinci dan instruktif. Ketika ia menunjukkan, C ++ tidak mempertimbangkan operator jenis konversi yang disediakan pengguna ketika kompilasi lhs = func(); (di mana func benar-benar nama struct) . Solusi saya agak berbeda - tidak lebih baik, hanya berbeda (meskipun didasarkan pada ide dasar yang sama).

Padahal saya ingin menulis ...

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

Saya berakhir dengan solusi yang menggunakan struct parameterized (dengan T = tipe pengembalian):

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

Manfaat dari solusi ini adalah bahwa kode apa pun yang menyertakan definisi templat ini dapat menambahkan lebih banyak spesialisasi untuk lebih banyak jenis. Anda juga dapat melakukan spesialisasi parsial struct jika diperlukan. Misalnya, jika Anda ingin penanganan khusus untuk jenis pointer:

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

Sebagai negatif, Anda tidak dapat menulis int x = func();dengan solusi saya. Anda harus menulis int x = func<int>();. Anda harus secara eksplisit mengatakan apa jenis kembalinya, daripada meminta kompiler untuk mengeluarkannya dengan melihat pada operator konversi jenis. Saya akan mengatakan bahwa solusi "saya" dan A.Rex keduanya berada di depan optimal untuk mengatasi dilema C ++ ini :)

Adam McKee
sumber
1

jika Anda ingin membebani metode dengan tipe pengembalian yang berbeda, cukup tambahkan parameter dummy dengan nilai default untuk memungkinkan eksekusi kelebihan, tetapi jangan lupa jenis parameter harus berbeda sehingga logika kelebihan bekerja selanjutnya adalah eg pada delphi:

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

gunakan seperti ini

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;
ZORRO_BLANCO
sumber
Itu ide yang buruk. Jangan masukkan parameter dummy, itu bau kode besar. Alih-alih, pilih nama yang berbeda, atau pilih jenis pengembalian yang dapat bertindak seperti, atau serikat yang didiskriminasi atau semacamnya.
Abel
@ Bel apa yang Anda sarankan sebenarnya adalah ide yang mengerikan, karena seluruh ide tentang parameter dummy ini, dan dinamai seperti itu untuk menjelaskan kepada pengembang bahwa parameter ini dummy dan harus diabaikan, juga jika Anda tidak tahu parameter dummy dengan nilai default digunakan di banyak perpustakaan, VCL dalam delphi, dan banyak IDE, misalnya dalam delphi Anda dapat melihatnya di unit sysutils di SafeLoadLibrary ...
ZORRO_BLANCO
Tentu saja ada skenario di mana parameter dummy berguna, seperti di lambdas dalam operasi peta atau lipat, atau ketika mengimplementasikan antarmuka. Tetapi hanya demi menciptakan kelebihan, tidak, saya mohon tidak setuju. Tidak perlu dan itu kebisingan yang programmer dapat hidup tanpanya.
Abel
0

Seperti yang sudah diperlihatkan - panggilan ambigu dari suatu fungsi yang hanya berbeda dengan tipe kembali memperkenalkan ambiguitas. Ambiguitas menginduksi kode yang rusak. Kode yang rusak harus dihindari.

Kompleksitas yang didorong oleh upaya ambiguitas menunjukkan bahwa ini bukan hack yang baik. Terlepas dari latihan intelektual - mengapa tidak menggunakan prosedur dengan parameter referensi.

procedure(reference string){};
procedure(reference int){};
string blah;
procedure(blah)
Tanpa kode
sumber
Karena Anda tidak dapat dengan mudah menggunakan kembali nilai "kembali" dengan segera. Anda harus melakukan setiap panggilan pada satu baris, sebagai lawan daridoing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
Adowrath
0

fitur kelebihan ini tidak sulit untuk dikelola, jika Anda melihatnya dengan cara yang sedikit berbeda. pertimbangkan yang berikut,

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

jika suatu bahasa memang mengembalikan kelebihan beban, itu akan memungkinkan kelebihan parameter, tetapi bukan duplikasi. ini akan memecahkan masalah:

main (){
f(x)
}

karena hanya ada satu f (int choice) untuk dipilih.

paulon0n
sumber
0

Di .NET, terkadang kami menggunakan satu parameter untuk menunjukkan output yang diinginkan dari hasil umum, dan kemudian melakukan konversi untuk mendapatkan apa yang kami harapkan.

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

Mungkin contoh ini juga bisa membantu:

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;
}
Facundo
sumber
1
Ini adalah jenis peretasan, dan dapat menyebabkan kesalahan run-time jika pengguna tidak memberikan hasil dengan benar, atau jika pengembang tidak benar mencocokkan jenis pengembalian dengan enum. Saya akan merekomendasikan menggunakan pendekatan berbasis template (atau parameter umum dalam C #?) Seperti dalam jawaban ini
sleblanc
0

Sebagai catatan, Oktaf memungkinkan hasil yang berbeda sesuai dengan elemen kembali menjadi skalar vs array.

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

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

Cf juga Dekomposisi Nilai Singular .

YvesgereY
sumber
0

Yang ini sedikit berbeda untuk C ++; Saya tidak tahu apakah itu akan dianggap kelebihan beban dengan mengetikkan kembali secara langsung. Ini lebih merupakan spesialisasi template yang bertindak dengan cara.

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

Contoh ini tidak persis menggunakan resolusi fungsi berlebih berdasarkan tipe kembali, namun c ++ kelas non objek ini menggunakan spesialisasi templat untuk mensimulasikan resolusi fungsi berlebih berdasarkan tipe balik dengan metode statis privat.

Setiap convertToTypefungsi memanggil templat fungsi stringToValue()dan jika Anda melihat rincian implementasi atau algoritme templat fungsi ini, ia memanggil getValue<T>( param, param )dan mengembalikan jenis Tdan menyimpannya ke dalam T*yang dilewatkan ke stringToValue()templat fungsi sebagai salah satu parameternya. .

Selain sesuatu seperti ini; C ++ tidak benar-benar memiliki mekanisme untuk memiliki resolusi fungsi overloading oleh tipe kembali. Mungkin ada konstruksi atau mekanisme lain yang tidak saya sadari yang dapat mensimulasikan resolusi berdasarkan tipe pengembalian.

Francis Cugler
sumber
-1

Saya pikir ini adalah GAP dalam definisi C ++ modern ... mengapa?

int func();
double func();

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

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

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

Mengapa kompiler C ++ tidak dapat melakukan kesalahan pada contoh "3" dan menerima kode pada contoh "1 + 2" ??

Andreas Otto
sumber
Ya, itulah yang mereka pertimbangkan pada saat itu untuk C # (dan mungkin C ++). Tetapi sementara kode Anda sepele, setelah Anda menambahkan hierarki kelas, metode virtual, abstrak dan antarmuka, kelebihan beban lainnya, dan terkadang beberapa pewarisan, menjadi sangat kompleks dengan sangat cepat untuk memutuskan metode mana yang harus diselesaikan. Ini adalah pilihan para desainer untuk tidak menempuh rute itu, tetapi bahasa lain telah memutuskan secara berbeda pada berbagai tingkat kesuksesan.
Abel
-2

Sebagian besar bahasa statis sekarang juga mendukung obat generik, yang akan menyelesaikan masalah Anda. Seperti yang dinyatakan sebelumnya, tanpa memiliki parameter diffs, tidak ada cara untuk mengetahui mana yang harus dihubungi. Jadi, jika Anda ingin melakukan ini, gunakan saja obat generik dan sebut itu sehari.

Charles Graham
sumber
Bukan hal yang sama. Bagaimana Anda menangani fungsi yang menerjemahkan input ke integer, float, bool, atau apa pun berdasarkan pada bagaimana tipe pengembalian digunakan? Itu tidak dapat digeneralisasi karena Anda memerlukan kasus khusus untuk masing-masingnya.
Jay Conrod
Lihat codeproject.com/KB/cpp/returnoverload.aspx untuk strategi pintar untuk "overloading on return type". Pada dasarnya, alih-alih mendefinisikan fungsi func (), Anda mendefinisikan func struct, berikan operator func () () dan konversi ke setiap jenis yang sesuai.
j_random_hacker
Jay, Anda menentukan jenis kembali saat Anda memanggil fungsi. Jika inpus berbeda, maka tidak ada masalah sama sekali. Jika ada yang sama, Anda bisa memiliki versi generik yang mungkin memiliki beberapa logika berdasarkan tipe menggunakan GetType ().
Charles Graham