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:
- Böyle bir durumda hangi işlevin çağrıldığını belirlemek için öngörülebilir bir yönteme sahip olun.
- 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(); }
.
- 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
, scalar
operatö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
. @a
Liste 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+@a
yazdırır. Ayrıca, her operatör bir bağlamı zorlayabilir , örneğin ekleme +
skaler bağlamı zorlar. man perlfunc
Belgelere her giriş bu. Örneğin, şunlar için girişin bir parçası glob EXPR
:
Liste bağlamında, EXPR
standart 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 perlfunc
diyor
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, localtime
bu nedenle örneği seçtim .
Bu davranışa sahip olan sadece yerleşikler değildir. Herhangi bir kullanıcı, wantarray
liste, 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ü readLn
olduğunu readLn :: Read a => IO a
. Bunun anlamı, olabilecek herhangi bir tür için Read
(resmi olarak, Read
tür sınıfının bir örneği olan her tür ) readLn
bunu okuyabilmesidir. Haskell bir kayan nokta sayısı okumak istediğimi nereden biliyordu? Eh, tipi sqrt
IS sqrt :: Floating a => a -> a
, esasen aracı sqrt
sadece 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
}