Dönüş tipine göre fonksiyon aşırı yüklenmesi?


252

Neden daha yaygın olarak statik olarak yazılan diller dönüş türüne göre işlev / yöntem aşırı yüklemesini desteklemiyor? Böyle bir şey düşünemiyorum. Parametre tipine göre aşırı yüklenmeyi desteklemekten daha az kullanışlı veya makul görünmüyor. Neden bu kadar az popüler?


Yanıtlar:


523

Diğerleri dönüş türüne göre aşırı, ne söylediğini aksine olan olası ve olan bazı modern diller tarafından yapılır. Her zamanki itiraz, koddaki gibi

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

hangisinin func()çağrıldığını söyleyemezsiniz . Bu birkaç yolla çözülebilir:

  1. Böyle bir durumda hangi işlevin çağrıldığını belirlemek için öngörülebilir bir yönteme sahip olun.
  2. Böyle bir durum meydana geldiğinde, derleme zamanı hatasıdır. Bununla birlikte, programlayıcının belirginleşmemesini sağlayan bir sözdizimine sahip olun, örn int main() { (string)func(); }.
  3. Yan etkileri yok. Yan etkileriniz yoksa ve asla bir işlevin dönüş değerini kullanmazsanız, derleyici işlevi ilk etapta çağırmaktan kaçınabilir.

Düzenli olarak kullandığım iki dilden ( ab ) dönüş türüne göre aşırı yük kullanıyorum: Perl ve Haskell . Ne yaptıklarını açıklayayım.

In Perl , arasında temel bir ayrım vardır skaler ve liste bağlamında (ve diğerleri, ama biz taklit edeceğiz iki vardır). Perl'deki her yerleşik işlev , çağrıldığı bağlama bağlı olarak farklı şeyler yapabilir . Örneğin join, scalaroperatör skaler bağlamı zorlarken, liste bağlamını (birleştirilen nesneye) zorlar, bu yüzden karşılaştırın:

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.

Perl'deki her operatör skaler bağlamda bir şey ve liste bağlamında bir şey yapar ve gösterildiği gibi farklı olabilir. (Bu sadece gibi rastgele işleçler için değildir localtime. @aListe bağlamında bir dizi kullanırsanız, diziyi döndürür, skaler bağlamda ise öğe sayısını döndürür. Böylece, örneğin , boyutu yazdırırken print @aöğeleri print 0+@ayazdırır. Ayrıca, her operatör bir bağlamı zorlayabilir , örneğin ekleme +skaler bağlamı zorlar. man perlfuncBelgelere her giriş bu. Örneğin, şunlar için girişin bir parçası glob EXPR:

Liste bağlamında, EXPRstandart Unix kabuğunun yaptığı gibi değere ilişkin (muhtemelen boş) bir dosya adı genişletmeleri listesi döndürür /bin/csh. Skaler bağlamda glob, bu tür dosya adı genişletmeleriyle yinelenir ve liste bittiğinde undef döndürülür.

Şimdi, liste ve skaler bağlam arasındaki ilişki nedir? Peki, man perlfuncdiyor

Aşağıdaki önemli kuralı unutmayın: Liste bağlamındaki bir ifadenin davranışını, skaler bağlamdaki davranışıyla ilişkilendiren bir kural yoktur. Tamamen farklı iki şey yapabilir. Her işleç ve işlev, skaler bağlamda döndürmenin en uygun değerin ne olduğuna karar verir. Bazı işleçler, liste bağlamında döndürülecek listenin uzunluğunu döndürür. Bazı operatörler listedeki ilk değeri döndürür. Bazı operatörler listedeki son değeri döndürür. Bazı operatörler bir dizi başarılı işlemi döndürür. Genel olarak, tutarlılık istemediğiniz sürece istediğinizi yaparlar.

bu yüzden tek bir işleve sahip olmak basit bir mesele değildir ve sonunda basit bir dönüşüm yaparsınız. Aslında, localtimebu nedenle örneği seçtim .

Bu davranışa sahip olan sadece yerleşikler değildir. Herhangi bir kullanıcı, wantarrayliste, skaler ve geçersiz içeriği ayırt etmenizi sağlayan böyle bir işlevi kullanarak tanımlayabilir . Örneğin, boş bağlamda çağrılıyorsanız hiçbir şey yapmaya karar verebilirsiniz.

Şimdi, bunun döndürülen değere göre gerçek aşırı yüklenme olmadığından şikayet edebilirsiniz, çünkü sadece bir fonksiyonunuz var, çağrıldığı bağlamı söyler ve daha sonra bu bilgiler üzerinde hareket eder. Bununla birlikte, bu açıkça eşdeğerdir (ve Perl'in her zamanki aşırı yüklemeye nasıl izin vermediğine benzer, ancak bir işlev sadece argümanlarını inceleyebilir). Dahası, bu yanıtın başında belirtilen belirsiz durumu güzel bir şekilde çözmektedir. Perl hangi yöntemi arayacağını bilmediğinden şikayet etmez; sadece çağırıyor. Tek yapması gereken, işlevin hangi bağlamda çağrıldığını bulmaktır, ki bu her zaman mümkündür:

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"

(Not: Bazen fonksiyon demek istediğimde Perl operatörü diyebilirim. Bu tartışma için çok önemli değil.)

Haskell diğer yaklaşımı yani yan etkilere sahip olmamayı tercih ediyor. Ayrıca güçlü bir tip sistemi vardır ve böylece aşağıdaki gibi kod yazabilirsiniz:

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

Bu kod standart girdiden bir kayan nokta sayısı okur ve karekökünü yazdırır. Ama bu konuda şaşırtıcı olan nedir? Eh, türü readLnolduğunu readLn :: Read a => IO a. Bunun anlamı, olabilecek herhangi bir tür için Read(resmi olarak, Readtür sınıfının bir örneği olan her tür ) readLnbunu okuyabilmesidir. Haskell bir kayan nokta sayısı okumak istediğimi nereden biliyordu? Eh, tipi sqrtIS sqrt :: Floating a => a -> a, esasen aracı sqrtsadece girdi olarak kayan nokta sayıları kabul ve Haskell ne istediğini anlaşılmaktadır yüzden olabilir.

Haskell istediğimi çıkartamazsa ne olur? Birkaç olasılık var. Dönüş değerini hiç kullanmazsam, Haskell ilk etapta işlevi çağırmaz. Ben Ancak, do dönüş değeri kullanmak, daha sonra Haskell o tür çıkaramayacağı anlamına şikayet:

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

Belirsizliği istediğiniz türü belirterek çözebilirim:

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

Her neyse, tüm bu tartışmanın anlamı, sorunuzun bir kısmını yanıtlayan dönüş değeri ile aşırı yüklenmenin mümkün ve yapıldığıdır.

Sorunuzun diğer kısmı neden daha fazla dilin bunu yapmadığıdır. Başkalarının buna cevap vermesine izin vereceğim. Bununla birlikte, birkaç yorum: temel neden muhtemelen karışıklık fırsatının burada argüman türüne göre aşırı yüklenmekten daha büyük olmasıdır. Tek tek dillerdeki rasyonellere de bakabilirsiniz:

Ada : "En aşırı aşırı yük çözümleme kuralının, aşırı yüklenmiş referansı çözmek için her şeyi - mümkün olduğunca geniş bir bağlamdan tüm bilgileri - kullanmak olduğu anlaşılabilir. Bu kural basit olabilir, ancak yararlı değildir. İnsan okuyucu gerektirir keyfi olarak büyük metin parçalarını taramak ve keyfi olarak karmaşık çıkarımlar yapmak için (yukarıdaki (g) gibi) Daha iyi bir kuralın görevi bir insan okuyucunun veya derleyicinin gerçekleştirmesi gereken bir kural haline getirdiğine ve bu görevi yerine getirdiğine inanıyoruz. insan okuyucu için mümkün olduğunca doğal. "

C ++ (Bjarne Stroustrup'un "C ++ Programlama Dili" alt bölümü 7.4.1): "Dönüş türleri aşırı yük çözünürlüğünde dikkate alınmaz. Bunun nedeni, bağımsız bir işleç veya işlev çağrısının bağlamdan bağımsız olarak çözümlenmesini sağlamaktır.

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

Dönüş türü dikkate alınırsa, artık tek başına bir çağrıya bakmak sqrt()ve hangi fonksiyonun çağrıldığını belirlemek mümkün olmaz . "(Karşılaştırma için Haskell'de örtük dönüşüm olmadığını unutmayın .)

Java ( Java Dil Spesifikasyonu 9.4.1 ): "Devralınan yöntemlerden biri, diğer her devralınan yöntem için döndürme türüyle değiştirilmelidir, aksi takdirde bir derleme zamanı hatası oluşur." (Evet, bunun bir gerekçe olmadığını biliyorum. Eminim, gerekçe Gosling tarafından "Java Programlama Dili" nde verilmiştir. Belki birisinin bir kopyası var mı? Ancak, Java hakkında eğlenceli gerçek: JVM dönüş değeri ile aşırı yüklemeye izin verir ! Bu, örneğin Scala'da kullanılır ve doğrudan Java üzerinden ve dahili araçlarla oynayarak erişilebilir .

PS. Son bir not olarak, aslında bir hile ile C ++ dönüş değeri ile aşırı yüklemek mümkündür. Tanık:

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
}

Harika gönderi, ancak okumanın ne olduğunu netleştirmek isteyebilirsiniz (String -> bir şey).
Thomas Eding

C ++ ayrıca const / const döndürülen değeri ile aşırı yükleyelim. stackoverflow.com/questions/251159/…
geon

3
Zorlama operatörlerini aşırı yüklediğiniz son hile için, "cout" hattı bazen çalışır, ancak kodda yaptığım hemen hemen her değişiklik, "operatör <<" için belirsiz aşırı yük verir.
Steve

1
Tercih edeceğim yaklaşım, bir aşırı yüklenmenin "tercih edilen" olarak işaretlenmesini gerektirecektir; derleyici sadece tercih edilen aşırı yükleri kullanarak bağlanarak başlayacak ve daha sonra tercih edilmeyen aşırı yüklerin bir gelişme olup olmayacağını belirleyecektir. Diğer şeylerin yanı sıra, türleri varsayalım Foove Barçift ​​yönlü dönüşümü destekliyoruz ve bir yöntem türü Foodahili olarak kullanıyor ancak türü döndürüyor Bar. Böyle bir yöntem, sonucu hemen türe zorlayacak kod tarafından çağrılırsa Foo, Bardönüş türünü kullanmak işe yarayabilir, ancak bu Foodaha iyi olur. BTW, ben de bir araç görmek istiyorum ...
supercat

... bir yöntem, bir yapıda hangi türün kullanılması gerektiğini belirleyebilir var someVar = someMethod();(veya geri dönüşünün bu şekilde kullanılmaması gerektiğini belirtebilir). Örneğin, bir Fluent arabirimi değişken ve değişmez sürümlerine sahip yarar olabilir aletlerin olduğu türlerinin bir aile, bu nedenle var thing2 = thing1.WithX(3).WithY(5).WithZ(9);olurdu WithX(3)kopyasını thing1bir değişken nesne, mutasyon X ve değişken nesne bunun iade etmek; WithY(5)Y'yi değiştirir ve aynı nesneyi döndürür; aynı şekilde WithZ (9). Sonra ödev değişmez bir türe dönüşür.
supercat

37

İşlevler dönüş türüne göre aşırı yüklenmişse ve bu iki aşırı yüklemeye sahipseniz

int func();
string func();

derleyicinin bu iki işlevden hangisini böyle bir çağrı gördüğünde arayacağını anlaması mümkün değildir

void main() 
{
    func();
}

Bu nedenle, dil tasarımcıları genellikle dönüş değeri aşırı yüklenmesine izin vermemektedir.

Bazı diller (örneğin MSIL gibi), ancak, do dönüş türüne göre aşırı izin verir. Onlar da elbette yukarıdaki zorluklarla karşı karşıya, ancak belgelerine danışmanız gereken geçici çözümler var.


4
Küçük bir kelime oyunu (cevabınız çok açık ve anlaşılır bir gerekçe veriyor): bunun bir yolu olmadığı değil; sadece yolların çoğu insanın istediğinden daha beceriksiz ve daha acı verici olması. Örneğin, C ++ 'da, aşırı yükleme, bazı çirkin döküm sözdizimi kullanılarak çözülebilirdi.
Michael Burr

2
@ Jörg W Mittag: Fonksiyonların ne yaptığını göremiyorsunuz. Kolayca farklı yan etkileri olabilir.
A. Rex

2
@ Jörg - anaakım programlama dillerinin çoğunda (C / C ++, C #, Java, vb.) İşlevlerin genellikle yan etkileri vardır. Aslında, yan etkileri olan fonksiyonların en azından olmayanlarla ortak olduğunu tahmin ediyorum.
Michael Burr

6
Burada geç atlama, ancak bazı bağlamlarda "işlev" (esasen) "yan etkisi olmayan bir yöntem" dar tanımına sahiptir. Daha genel olarak, "işlev" genellikle "yöntem" veya "altyordam" ile dönüşümlü olarak kullanılır. Jorg bakış
açınıza

3
Daha sonra atlama, bazı bakış açıları titiz veya bilgiçlik dışında sıfatlar kullanabilir
Patrick McDonald

27

Böyle bir dilde, aşağıdakileri nasıl çözersiniz:

f(g(x))

eğer faşırı yüklenmeleri vardı void f(int)ve void f(string)ve gaşırı yüklenmeleri vardı int g(int)ve string g(int)? Bir çeşit dezavantajcıya ihtiyacınız olacak.

Sanırım buna ihtiyaç duyabileceğiniz durumlar, işlev için yeni bir ad seçerek daha iyi sunulabilir.


2
Düzenli aşırı yüklenme de belirsizlikler ile sonuçlanabilir. Bunların normalde gerekli olan atımların sayısını sayarak çözüldüğünü düşünüyorum, ancak bu her zaman işe yaramaz.
Jay Conrod

1
evet, standart dönüşümler tam eşleme, tanıtım ve dönüşüm olarak sıralanır: void f (int); geçersiz f (uzun); f ( 'a'); f (int) çağırır, çünkü bu sadece bir promosyon, uzun dönüşümü ise dönüşümdür. void f (şamandıra); void f (kısa); f (10); her ikisi için de dönüşüm gerekir: çağrı belirsiz.
Johannes Schaub - litb

Dilin tembel bir değerlendirmesi varsa, bu bir sorun kadar değildir.
JDD

Upvote, parametre tipi aşırı yükleme ve dönüş tipi aşırı yükleme etkileşimi Rex'in gönderisinde ele alınmadı. Çok iyi bir nokta.
Joseph Garvin

1
Bir dil tasarlıyorsam, kuralım aşırı yüklenmiş herhangi bir işlev için, her parametre imzasının varsayılan olarak belirtilen bir dönüş türüne sahip olması olurdu; her işlev çağrısının varsayılan türü kullanacağını varsayarak derleyici başlar. Bununla birlikte, bir kez yapıldıktan sonra, bir fonksiyonun dönüş değerinin hemen başka bir şeye atıldığı veya başka bir şeye zorlandığı her durumda, derleyici parametre imzası aynı olan, ancak dönüş tipi daha iyi bir eşleşme (veya muhtemelen geçersiz) olan bir aşırı yükü kontrol eder. . Muhtemelen bu tür aşırı yüklemeler için bir "geçersiz kılma - bir geçersiz kılma" kuralı dayatırım.
Supercat

19

Çok benzer bir sorudan (dupe?) C ++ 'a özgü bir cevap çalmak için :


Fonksiyon geri dönüş tipleri aşırı yük çözünürlüğünde devreye girmez çünkü Stroustrup (diğer C ++ mimarlarından gelen girdilerle varsayılır) aşırı yük çözünürlüğünün 'bağlamdan bağımsız' olmasını istedi. Bkz. 7.4.1 - "C ++ Programlama Dili, Üçüncü Baskı" dan "Aşırı Yükleme ve Dönüş Türü".

Bunun nedeni, bağımsız bir operatör veya işlev çağrısı için bağlamdan bağımsız bir çözüm sağlamaktır.

Sadece aşırı yükün nasıl adlandırıldığına dayanmasını istediler - sonucun nasıl kullanıldığına değil (hiç kullanılmışsa). Gerçekten de, sonuç kullanılmadan birçok işlev çağrılır veya sonuç daha büyük bir ifadenin parçası olarak kullanılır. Buna karar verdiklerinde emin olduğum bir faktör, dönüş tipi çözünürlüğün bir parçasıysa, karmaşık kurallarla çözülmesi gereken veya derleyici atması gereken aşırı yüklenmiş işlevlere çok sayıda çağrı olacağıydı. çağrının belirsiz olduğu bir hata.

Ve Lord, C ++ aşırı yük çözünürlüğünün durduğu kadar karmaşık olduğunu biliyor ...


5

Haskell'de fonksiyon aşırı yüklemesi olmamasına rağmen mümkündür. Haskell tip sınıflarını kullanır. Bir programda şunları görebilirsiniz:

class Example a where
    example :: Integer -> a

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

İşlev aşırı yüklenmesi o kadar popüler değildir. Çoğunlukla onunla gördüğüm diller C ++, belki de java ve / veya C #. Tüm dinamik dillerde kısayol:

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

Dolayısıyla bunun pek bir anlamı yok. Çoğu kişi, dilin, kullandığınız her yerde tek bir satır bırakmanıza yardımcı olup olamayacağıyla ilgilenmez.

Örüntü eşleme işlevi aşırı yüklenmeye biraz benzer ve sanırım bazen benzer şekilde çalışır. Yine de bu yaygın değildir, çünkü sadece birkaç program için yararlıdır ve çoğu dilde uygulanması zordur.

Aşağıdakiler de dahil olmak üzere dile uygulanacak sonsuz sayıda daha iyi uygulanması daha kolay özellik olduğunu görüyorsunuz:

  • Dinamik yazma
  • Listeler, sözlükler ve unicode dizeler için dahili destek
  • Optimizasyonlar (JIT, tip çıkarım, derleme)
  • Entegre dağıtım araçları
  • Kütüphane desteği
  • Topluluk desteği ve toplanma yerleri
  • Zengin standart kütüphaneler
  • İyi sözdizimi
  • Eval baskı döngüsünü okuyun
  • Yansıtıcı programlama desteği

3
Haskell aşırı yükleniyor. Tür sınıfları, aşırı yüklenmiş işlevleri tanımlamak için kullanılan dil özelliğidir.
Lii

2

İyi cevaplar! A.Rex'in cevabı özellikle çok ayrıntılı ve öğreticidir. O işaret ettiği gibi, C ++ gelmez derlerken kullanıcı tarafından sağlanan tip-dönüşüm operatörleri düşünün lhs = func(); (fonk gerçekten yapının adıdır) . Geçici çözümüm biraz farklı - daha iyi değil, sadece farklı (aynı temel fikri temel almasına rağmen).

Oysa ben yazmak istemiştim ...

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

Ben parametreli bir yapı (T = dönüş türü) kullanan bir çözüm ile sona erdi:

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

Bu çözümün bir yararı, bu şablon tanımlarını içeren herhangi bir kodun daha fazla tür için daha fazla uzmanlık ekleyebilmesidir. Ayrıca, yapının kısmi uzmanlıklarını gerektiği gibi yapabilirsiniz. Örneğin, işaretçi türleri için özel işlem istiyorsanız:

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

Negatif olarak, int x = func();benim çözümümle yazamazsınız . Yazmak zorundasın int x = func<int>();. Derleyiciden tür dönüştürme işleçlerine bakarak onu suss etmesini istemek yerine, dönüş türünün ne olduğunu açıkça söylemelisiniz. "Benim" çözümüm ve A.Rex'in her ikisinin de bu C ++ ikilemiyle başa çıkmanın yollarının pareto-optimal cephesinde olduğunu söyleyebilirim :)


1

farklı dönüş türlerine sahip yöntemleri aşırı yüklemek istiyorsanız, aşırı yük yürütülmesine izin vermek için varsayılan değere sahip bir kukla parametre ekleyin , ancak parametre türünün farklı olması gerektiğini unutmayın, bu nedenle aşırı yük mantığı bir sonraki delphi'de çalışır:

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

böyle kullan

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;

Bu korkunç bir fikir. Sahte parametreler kullanmayın, bu büyük bir kod kokusudur. Bunun yerine, farklı adlar seçin veya gibi davranabilen veya ayrımcı bir birlik veya başka bir şey olan bir dönüş türü seçin.
Abel

@Abel önerdiğiniz şey aslında korkunç bir fikir, çünkü tüm fikir bu kukla parametre hakkındadır ve geliştirici için bu parametrenin kukla olduğunu ve göz ardı edilmesi gerektiğini açıkça belirtmek için böyle adlandırılmıştır. varsayılan değerlere sahip kukla parametreleri bilmiyorum birçok kütüphanede, VCL delphi ve birçok IDE, örneğin delphi içinde SafeLoadLibrary sysutils biriminde görebilirsiniz ...
ZORRO_BLANCO

Harita veya katlama işlemlerinde lambdalarda veya bir arabirimi uygularken olduğu gibi, kukla parametrelerin yararlı olduğu senaryolar kesinlikle vardır. Ama sadece aşırı yük yaratma uğruna, hayır, katılmıyorum yalvarıyorum. Programcıların onsuz yaşayabileceği bir gürültü yoktur.
Abel

0

Daha önce gösterildiği gibi - yalnızca dönüş türüne göre farklılık gösteren bir fonksiyonun belirsiz çağrıları belirsizliği ortaya çıkarır. Belirsizlik arızalı kodu tetikler. Arızalı koddan kaçınılmalıdır.

Belirsizlik denemesinin getirdiği karmaşıklık, bunun iyi bir saldırı olmadığını göstermektedir. Entelektüel alıştırma dışında - neden referans parametreli prosedürler kullanılmıyor?

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

Çünkü "dönüş" değerlerini hemen tekrar kullanamazsınız. Bunun yerine, her çağrıyı tek bir hatta doing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
yapmanız

0

biraz farklı bir şekilde bakarsanız, bu aşırı yükleme özelliğini yönetmek zor değildir. aşağıdakileri göz önünde bulundur,

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

bir dil aşırı yüklemeyi döndürürse, parametre aşırı yüklemesine izin verir, ancak yinelemelere izin vermez. bu şu sorunu çözecektir:

main (){
f(x)
}

çünkü aralarından seçim yapabileceğiniz tek bir f (int seçim) var.


0

.NET'te, bazen genel bir sonuçtan istenen çıktıyı belirtmek için bir parametre kullanırız ve daha sonra beklediğimizi elde etmek için bir dönüşüm yaparız.

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

Belki bu örnek de yardımcı olabilir:

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
Bu bir tür hackish ve kullanıcı sonucu düzgün bir şekilde yayınlamazsa veya geliştirici dönüş türlerini enum ile düzgün eşleştirmezse çalışma zamanı hatalarına neden olabilir. Ben bu cevap
sleblanc

0

Kayıt için Octave , dönüş elemanının dizi ile skaler olmasına göre farklı sonuçlara izin verir.

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

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

Ayrıca Tekil Değer Ayrışımı .


0

Bu C ++ için biraz farklıdır; Doğrudan dönüş tipine göre aşırı yükleme düşünülüp düşünülmeyeceğini bilmiyorum. Daha çok bir şekilde davranan bir şablon uzmanlığıdır.

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

Bu örnek, dönüş türüne göre işlev aşırı yük çözünürlüğünü tam olarak kullanmaz, ancak bu c ++ nesne olmayan sınıf, özel statik yöntemle dönüş türüne göre işlev aşırı yük çözünürlüğünü simüle etmek için şablon uzmanlığını kullanır.

Her convertToTypefonksiyonları fonksiyon şablonu aradığınız stringToValue()ve bunu çağırıyor bu fonksiyon şablonunun uygulama ayrıntıları veya algoritma bakarsanız getValue<T>( param, param )ve bir tür geri dönüyor Tve bir içine saklayarak T*geçirilir o stringToValue()onun parametrelerden biri olarak işlev şablonunun .

Bunun gibi bir şey dışında; C ++, dönüş tipine göre aşırı yük çözünürlüğüne sahip bir mekanizmaya sahip değildir. Dönüş türüne göre çözünürlüğü simüle edebileceğinin farkında olmadığım başka yapılar veya mekanizmalar olabilir.


-1

Bu modern C ++ tanımında bir GAP olduğunu düşünüyorum ... neden?

int func();
double func();

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

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

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

Neden bir C ++ derleyicisi "3" örneğinde hata atamaz ve "1 + 2" örneğindeki kodu kabul edemez?


Evet, C # (ve belki de C ++) için düşünüyorlardı. Ancak kodunuz önemsiz olsa da, sınıf hiyerarşileri, sanal yöntemler, özetler ve arayüzler, diğer aşırı yükler ve bazen birden fazla kalıtım eklediğinizde, hangi yöntemin çözülmesi gerektiğine karar vermek çok hızlı bir şekilde karmaşıklaşır. Tasarımcıların bu rotaya gitmemeleri için bir seçim, ancak diğer diller çeşitli başarı seviyelerinde farklı kararlar verdi.
Abel

-2

Çoğu statik dil artık sorununuzu çözecek jenerikleri de desteklemektedir. Daha önce belirtildiği gibi, parametre farkları olmadan, hangisini arayacağınızı bilmenin yolu yoktur. Bunu yapmak istiyorsanız, jenerikleri kullanın ve bir gün deyin.


Aynı şey değil. Girdiyi tamsayıya, kayan noktaya, boole veya dönüş türünün nasıl kullanıldığına bağlı olan her şeye çeviren bir işlevi nasıl ele alırsınız? Her biri için özel bir davaya ihtiyacınız olduğu için genelleştirilemez.
Jay Conrod

"Dönüş tipinde aşırı yükleme" için akıllı bir strateji için codeproject.com/KB/cpp/returnoverload.aspx adresine bakın . Temel olarak, bir işlev fonk () tanımlamak yerine, bir yapı fonk tanımlar, ona bir operatör () () ve her uygun türe dönüşüm sağlarsınız.
j_random_hacker

Jay, işlevi çağırdığınızda dönüş türünü tanımlarsınız. İnpus farklıysa, hiçbir sorun yoktur. Aynı varsa, GetType () kullanarak türe dayalı bazı mantık olabilecek genel bir sürümünüz olabilir.
Charles Graham
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.