D.ershane D Programlama Dili Dersleri

arayüz: [interface], yapının, sınıfın, veya modülün sunduğu işlevler
birlik: [union], birden fazla değişkeni aynı bellek bölgesinde depolayan veri yapısı
blok: [block], küme parantezleriyle gruplanmış ifadelerin tümü
CTFE: [Compile Time Function Evaluation], derleme zamanında işlev işletme
çokuzlu: [tuple], bir kaç parçanın diziye benzer şekilde bir araya gelmesinden oluşan yapı
hazır değer: [literal], kod içinde hazır olarak yazılan değerler
isim alanı: [name space], ismin geçerli olduğu kapsam
kapsam: [scope], küme parantezleriyle belirlenen bir alan
kısıtlama: [constraint], şablon parametrelerinin uyması gereken koşulların belirlenmesi
özelleme: [specialization], şablonun bir özel tanımı
özyineleme: [recursion], bir işlevin doğrudan veya dolaylı olarak kendisini çağırması
sıradüzen: [hierarchy], sınıfların türeyerek oluşturdukları aile ağacı
şablon: [template], derleyicinin örneğin 'türden bağımsız programlama' için kod üretme düzeneği
... bütün sözlük

Bölümler
İngilizce Kaynaklar
Diğer



Ayrıntılı Şablonlar

Şablonların ne kadar kullanışlı olduklarını Şablonlar dersinde görmüştük. Algoritmaların veya veri yapılarının tek tanımını yazarak birden çok türle çalışmalarını sağlayabiliyorduk.

O derste şablonların en çok karşılaşılan kullanımlarını göstermiştim. Bu derste şablon olanağını daha ayrıntılı olarak göreceğiz. Devam etmeden önce en azından o dersin sonundaki özeti bir kere daha gözden geçirmenizi öneririm; o derste anlatılanları burada tekrarlamamaya çalışacağım.

Daha önce işlev, yapı, ve sınıf şablonlarını tanımıştık ve şablon parametrelerinin türler konusunda serbestlik getirdiklerini görmüştük. Bu derste; hem birlik ve arayüz şablonlarını da tanıyacağız; hem de şablon parametrelerinin değer, this, alias, ve çokuzlu çeşitleri olduğunu da göreceğiz.

Kestirme ve uzun söz dizimi

C++ gibi başka dillerde de olduğu gibi D'nin şablonları çok güçlü olanaklardır. Buna rağmen, en çok yararlanılan kullanımlarının olabildiğince rahat ve anlaşılır olmasına çalışılmıştır. İşlev, yapı, veya sınıf şablonu tanımlamak; isminden sonra şablon parametre listesi eklemek kadar kolaydır:

T ikiKatı(T)(T değer)
{
    return 2 * değer;
}

class Kesirli(T)
{
    T pay;
    T payda;

    // ...
}

Daha önce de görmüş olduğunuz yukarıdaki tanımlar, D'nin kestirme şablon tanımlarıdır.

Aslında şablonlar daha uzun olarak template anahtar sözcüğü ile tanımlanırlar. Yukarıdaki söz dizimleri, aşağıdaki tanımların kısa eşdeğerleridir:

template ikiKatı(T)
{
    T ikiKatı(T değer)
    {
        return 2 * değer;
    }
}

template Kesirli(T)
{
    class Kesirli
    {
        T pay;
        T payda;

        // ...
    }
}

Derleyicinin her zaman için uzun tanımı kullandığını, ve kestirme söz dizimini arka planda şu şekilde uzun tanıma dönüştürdüğünü düşünebiliriz:

  1. tanımladığımız şablonu bir template kapsamı içine alır
  2. o kapsama da aynı ismi verir
  3. şablon parametre listesini bizim tanımladığımız şablondan alır ve o kapsama verir

Kestirme tanım; biraz aşağıda göreceğimiz tek tanım içeren şablon olanağı ile ilgilidir.

Şablon isim alanı

template bloğu, aslında bir seferde birden çok şablon tanımlanmasına da olanak verir:

template ŞablonBloğu(T)
{
    T birİşlev(T değer)
    {
        return değer / 3;
    }

    struct BirYapı
    {
        T üye;
    }
}

Yukarıdaki blokta bir işlev, bir de yapı şablonu tanımlamaktadır. O şablonları örneğin int ve double türleri için, ve uzun isimleriyle şöyle kullanabiliriz:

    auto sonuç = ŞablonBloğu!int.birİşlev(42);
    writeln(sonuç);

    auto nesne = ŞablonBloğu!double.BirYapı(5.6);
    writeln(nesne.üye);

Şablonun belirli bir türle kullanımı, bir isim alanı tanımlar. Bloğun içindeki isimler, o isim alanı açıkça belirtilerek kullanılabilirler. Bu isimler fazla uzun olabileceklerinden, onlara alias ve alias this dersinde gördüğümüz alias anahtar sözcüğü ile kısa takma isimler verilebilir:

    alias ŞablonBloğu!dchar.BirYapı KarakterYapısı;

// ...

    auto nesne = KarakterYapısı('ğ');
    writeln(nesne.üye);
Tek tanım içeren template blokları

İçinde tek tanım bulunan bir şablon, eğer şablon bloğunun ismi o tek tanımın ismi ile aynı ise, doğrudan o tek tanım yerine kullanılabilir. Bu, şimdiye kadarki şablonlarda kullandığımız kestirme söz dizimini sağlayan olanaktır.

Örnek olarak, büyüklüğü 20 bayttan fazla olan türlerin büyük olarak kabul edildiği bir program olsun. Bir türün büyük olup olmadığının kararı şöyle bir şablonun içindeki bir bool değişken ile belirlenebilir:

template büyük_mü(T)
{
    const bool büyük_mü = T.sizeof > 20;
}

Dikkat ederseniz, hem şablonun hem de içindeki tek tanımın isimleri aynıdır. Öyle olduğunda, bu uzun şablon tanımının isim alanı ve içindeki tek tanım açıkça büyük_mü!int.büyük_mü diye yazılmaz; kısaca, yalnızca şablonun isim alanı yazılır:

    writeln(büyük_mü!int);

Yukarıdaki işaretli bölüm, şablon içindeki aynı isimli bool yerine geçer. O kod, çıktıya false yazar; çünkü büyük_mü!int, şablon içindeki bool değişkendir. int'in uzunluğu 4 bayt olduğu için o bool değişkenin değeri false'tur.

Şablon çeşitleri
İşlev, sınıf, ve yapı şablonları

Bu alt başlığı bütünlük amacıyla yazdım.

Yukarıda da görüldüğü gibi, bu tür şablonlarla hem Şablonlar dersinde hem de daha sonraki örneklerde çok karşılaştık.

Birlik şablonları

Birlik şablonları, yapı şablonları ile aynı şekilde tanımlanırlar. Birlik şablonları için de kestirme şablon söz dizimi kullanılabilir.

Bir örnek olarak, Birlikler dersinde tanımladığımız IpAdresi birliğinin daha genel ve daha kullanışlı olanını tasarlamaya çalışalım. O dersteki birlik; değer olarak uint türünü kullanıyordu. O değerin parçalarına erişmek için kullanılan dizinin elemanlarının türü de ubyte idi:

union IpAdresi
{
    uint değer;
    ubyte[4] baytlar;
}

O birlik, hem IP adresi değeri tutuyordu, hem de o değerin parçalarına ayrı ayrı erişme olanağı veriyordu.

Aynı kavramı, daha genel isimler de kullanarak bir şablon olarak şöyle tanımlayabiliriz:

union ParçalıDeğer(AsılTür, ParçaTürü)
{
    AsılTür değer;
    ParçaTürü[/* gereken eleman adedi */] parçalar;
}

Bu birlik şablonu, asıl değerin ve alt parçalarının türünü serbestçe tanımlama olanağı verir. Asıl tür ve parça türü, birbirlerinden bağımsız olarak seçilebilirler.

Burada gereken bir işlem, parça dizisinin uzunluğunun kullanılan türlere bağlı olarak hesaplanmasıdır. IpAdresi birliğinde, uint'in dört adet ubyte parçası olduğunu bildiğimiz için sabit olarak 4 yazabilmiştik. Bu şablonda ise dizinin uzunluğu, kullanılan türlere göre otomatik olarak hesaplanmalıdır.

Türlerin bayt olarak uzunlukları .sizeof niteliğinden öğrenilebildiği için; bu bilgiden yararlanırsak, uzunluk hesabını bir işlev şablonuna yaptırabiliriz:

int elemanAdedi(AsılTür, ParçaTürü)()
{
    return
        (AsılTür.sizeof + (ParçaTürü.sizeof - 1))
        / ParçaTürü.sizeof;
}

Not: O hesaptaki (ParçaTürü.sizeof - 1) ifadesi, türlerin uzunluklarının birbirlerine tam olarak bölünemediği durumlarda gerekir. Asıl türün 5 bayt, parça türünün 2 bayt olduğunu düşünün. O ifadeyi eklemezsek; gerçekte 3 adet parça gerektiği halde, 5/2 hesabının sonucu tamsayı kırpılması nedeniyle 2 çıkardı.

Artık parça dizisinin eleman adedi olarak o işlevin döndürdüğü değeri kullanabiliriz ve böylece birliğin tanımı tamamlanmış olur:

union ParçalıDeğer(AsılTür, ParçaTürü)
{
    AsılTür değer;
    ParçaTürü[elemanAdedi!(AsılTür, ParçaTürü)()] parçalar;
}

Daha önce tanımladığımız IpAdresi birliğinin eşdeğeri olarak bu şablonu kullanmak istesek, türleri IpAdresi'nde olduğu gibi sırasıyla uint ve ubyte olarak belirtmemiz gerekir:

    auto adres = ParçalıDeğer!(uint, ubyte)(0xc0a80102);

    foreach (eleman; adres.parçalar) {
        write(eleman, ' ');
    }

Birlikler dersinde gördüğümüz çıktının aynısını elde ederiz:

2 1 168 192

Bu şablonun getirdiği esnekliği görmek için IP adresinin parçalarını iki adet ushort olarak edinmek istediğimizi düşünelim. Bu sefer, ParçalıDeğer şablonunun ParçaTürü parametresi olarak ushort yazmak yeterlidir:

    auto adres = ParçalıDeğer!(uint, ushort)(0xc0a80102);

Alışık olmadığımız bir düzende olsa da, bu seferki çıktı iki ushort'tan oluşmaktadır:

258 49320

Bir sonraki başlığa geçmeden önce, burada başka bir tasarımın da kullanılabileceğini göstermek istiyorum. Kaç adet parça gerektiğini yukarıdaki elemanAdedi işlev şablonu ile hesapladığımız için, o işlevi çağırmak için sonuna () karakterleri yazmıştık:

    ParçaTürü[elemanAdedi!(AsılTür, ParçaTürü)()] parçalar;

Aynı hesabın sonucunu, tek tanımlı bir şablon içindeki değişken değeri olarak da saklayabilirdik:

template elemanAdedi(AsılTür, ParçaTürü)
{
    const int elemanAdedi =
        (AsılTür.sizeof + (ParçaTürü.sizeof - 1))
        / ParçaTürü.sizeof;
}

O tek tanımlı şablon, içindeki int değişken yerine geçtiği için, doğrudan o değişkenin değerinden yararlanabilirdik:

    ParçaTürü[elemanAdedi!(AsılTür, ParçaTürü)] parçalar;
Arayüz şablonları

Arayüz şablonları, arayüzde kullanılan türler konusunda serbestlik getirirler. Arayüz şablonlarında da kestirme tanım kullanılabilir.

Örnek olarak, renkli nesnelerin arayüzünü tanımlayan, ama renk olarak hangi türün kullanılacağını serbest bırakan bir arayüz tasarlayalım:

interface RenkliNesne(RenkTürü)
{
    void renklendir(RenkTürü renk);
}

O arayüz, kendisinden türeyen sınıfların renklendir işlevini tanımlamalarını gerektirir; ama renk olarak ne tür kullanılacağı konusunu serbest bırakır.

Bir sitedeki bir çerçeveyi temsil eden bir sınıf; renk olarak kırmızı, yeşil, ve maviden oluşan üçlü bir yapı kullanabilir:

struct KırmızıYeşilMavi
{
    ubyte kırmızı;
    ubyte yeşil;
    ubyte mavi;
}

class SiteÇerçevesi : RenkliNesne!KırmızıYeşilMavi
{
    override void renklendir(KırmızıYeşilMavi renk)
    {
        // ...
    }
}

Öte yandan, renk olarak ışığın frekansını kullanmak isteyen bir sınıf, renk için frekans değerine uygun olan başka bir türden yararlanabilir:

alias double Frekans;

class Lamba : RenkliNesne!Frekans
{
    override void renklendir(Frekans renk)
    {
        // ...
    }
}

Yine Şablonlar dersinden hatırlayacağınız gibi, "her şablon gerçekleştirmesi farklı bir türdür". Buna göre, RenkliNesne!KırmızıYeşilMavi ve RenkliNesne!Frekans arayüzleri, farklı arayüzlerdir. Bu yüzden, onlardan türeyen sınıflar da birbirlerinden bağımsız sıradüzenlerin parçaları olurlar; SiteÇerçevesi ve Lamba, birbirlerinden bağımsızdır.

Şablon parametre çeşitleri

Şimdiye kadar gördüğümüz şablonlar, hep türler konusunda serbestlik getiriyorlardı.

Yukarıdaki örneklerde de kullandığımız T ve RenkTürü gibi şablon parametreleri, hep türleri temsil ediyorlardı. Örneğin T'nin anlamı, şablonun kod içindeki kullanımına bağlı olarak int, double, Öğrenci, vs. gibi bir tür olabiliyordu.

Şablon parametreleri; değer, this, alias, ve çokuzlu da olabilirler.

Tür parametreleri

Bu alt başlığı bütünlük amacıyla yazdım.

Şimdiye kadar gördüğümüz bütün şablon parametreleri zaten hep tür parametreleriydi.

Değer parametreleri

Şablon parametresi olarak değerler de kullanılabilir. Bu, şablonun tanımı ile ilgili bir değerin serbest bırakılmasını sağlar.

Şablonlar derleme zamanı olanakları oldukları için, değer olarak kullanılan şablon parametresinin derleme zamanında hesaplanabilmesi şarttır. Bu yüzden, programın çalışması sırasında hesaplanan, örneğin girişten okunan bir değer kullanılamaz.

Bir örnek olarak, belirli sayıda köşeden oluşan şekilleri temsil eden yapılar tanımlayalım:

struct Üçgen
{
    Nokta[3] köşeler;
// ...
}

struct Dörtgen
{
    Nokta[4] köşeler;
// ...
}

struct Beşgen
{
    Nokta[5] köşeler;
// ...
}

Örnek kısa olsun diye başka üyelerini göstermedim. Normalde, o türlerin başka üyelerinin ve işlevlerinin de bulunduğunu ve hepsinde tamamen aynı şekilde tanımlandıklarını varsayalım. Sonuçta, dizi uzunluğunu belirleyen değer dışında, o yapıların tanımları aynı olsun.

Değer şablon parametreleri böyle durumlarda yararlıdır. Yukarıdaki tanımlar yerine tek yapı şablonu tanımlanabilir. Yeni tanım genel amaçlı olduğu için, ismini de o şekillerin genel ismi olan poligon koyarak şöyle tanımlayabiliriz:

struct Poligon(int köşeAdedi)
{
    Nokta[köşeAdedi] köşeler;
// ...
}

O yapı şablonu, şablon parametresi olarak int türünde ve köşeAdedi isminde bir şablon parametresi almaktadır. O parametre değeri, yapının tanımında herhangi bir yerde kullanılabilir.

Artık o şablonu istediğimiz sayıda köşesi olan poligonları ifade etmek için kullanabiliriz:

    Poligon!100 yüzKöşeli;

Yine alias'tan yararlanarak kullanışlı isimler verebiliriz:

alias Poligon!3 Üçgen;
alias Poligon!4 Dörtgen;
alias Poligon!5 Beşgen;

// ...

    Üçgen üçgen;
    Dörtgen dörtgen;
    Beşgen beşgen;

Böylece; hem başlangıçtaki üç farklı yapı yerine tek şablon tanımlamış oluruz, hem de herhangi sayıda köşesi olan şekiller için kullanabiliriz.

Yukarıdaki değer şablon parametresinin türü int'ti. Derleme zamanında bilinebildiği sürece, değer olarak başka türler de kullanılabilir.

Başka bir örnek olarak, basit XML elemanları oluşturmakta kullanılan bir sınıf şablonu tasarlayalım. Bu basit XML tanımı, çok basitçe şu çıktıyı üretmek için kullanılsın:

Örneğin değeri 42 olan bir elemanın <isim>42</isim> şeklinde görünmesini isteyelim.

Eleman isimlerini bir sınıf şablonunun string türündeki bir değer parametresi olarak belirleyebiliriz:

class XmlElemanı(string isim)
{
    double değer;

    this(double değer)
    {
        this.değer = değer;
    }

    override string toString() const
    {
        return format("<%s>%s</%s>", isim, değer, isim);
    }
}

Bu örnekteki şablon parametresi, şablonda kullanılan bir türle değil, bir string değeriyle ilgilidir. O string'in değeri de şablon içinde gereken her yerde kullanılabilir.

alias'tan yararlanarak kullanışlı tür isimleri de tanımlayarak:

alias XmlElemanı!"konum" Konum;
alias XmlElemanı!"sıcaklık" Sıcaklık;
alias XmlElemanı!"ağırlık" Ağırlık;

void main()
{
    Object[] elemanlar;

    elemanlar ~= new Konum(1);
    elemanlar ~= new Sıcaklık(23);
    elemanlar ~= new Ağırlık(78);

    writeln(elemanlar);
}

Not: Ben bu örnekte kısa olsun diye ve nasıl olsa bütün sınıf sıradüzenlerinin en üstünde bulunduğu için bir Object dizisi kullandım. O sınıf şablonu aslında daha uygun bir arayüz sınıfından da türetilebilirdi.

Yukarıdaki kodun çıktısı:

<konum>1</konum> <sıcaklık>23</sıcaklık> <ağırlık>78</ağırlık>

Değer parametrelerinin de varsayılan değerleri olabilir. Örneğin, herhangi boyutlu bir uzaydaki noktaları temsil eden bir yapı tasarlayalım. Noktaların koordinat değerleri için kullanılan tür ve uzayın kaç boyutlu olduğu, şablon parametreleri ile belirlensin:

struct Konum(T, int boyut = 3)
{
    T[boyut] koordinatlar;
}

boyut parametresinin varsayılan bir değerinin bulunması, bu şablonun o parametre belirtilmeden de kullanılabilmesini sağlar:

    Konum!double merkez;    // üç boyutlu uzayda bir nokta

Gerektiğinde farklı bir değer de belirtilebilir:

    Konum!(int, 2) nokta;   // iki boyutlu düzlemde bir nokta
Üye işlev şablonları için this parametreleri

Üye işlevler de şablon olarak tanımlanabilirler. Üye işlev şablonlarının da tür ve değer parametreleri bulunabilir, ve normal işlev şablonlarından beklendiği gibi çalışırlar.

Ek olarak; üye işlev şablonlarının parametreleri, this anahtar sözcüğü ile de tanımlanabilir. Bu durumda, o anahtar sözcükten sonra yazılan isim, o nesnenin this referansının türü haline gelir. (Not: Burada, çoğunlukla kurucu işlevler içinde gördüğümüz this.üye = değer kullanımındaki this referansından, başka bir deyişle nesnenin kendisini ifade eden referanstan bahsediyorum.)

struct BirYapı
{
    void birİşlev(this KendiTürüm)() const
    {
        writeln("Bu nesnenin türü: ", typeid(KendiTürüm));
    }
}

O üye işlev şablonunun KendiTürüm parametresi, nesnenin asıl türüdür:

    auto değişebilen = BirYapı();
    const sabit = BirYapı();
    immutable değişmez = BirYapı();

    değişebilen.birİşlev();
    sabit.birİşlev();
    değişmez.birİşlev();

KendiTürüm; nesnenin const, immutable, vs. tür belirteçlerine de sahiptir:

Bu nesnenin türü: deneme.BirYapı
Bu nesnenin türü: const(deneme.BirYapı)
Bu nesnenin türü: immutable(deneme.BirYapı)
alias parametreleri

alias şablon parametrelerine karşılık olarak D programlarında geçebilen bütün yasal sözcükler kullanılabilir. Bu sözcükler evrensel isimler, yerel isimler, modül isimleri, başka şablon isimleri, vs. olabilirler. Tek koşul, o parametrenin şablon içindeki kullanımının o parametreye uygun olmasıdır.

Örnek olarak, hangi yerel değişkeni değiştireceği kendisine bir alias parametre olarak bildirilen bir yapıya bakalım:

struct BirYapı(alias değişken)
{
    void birİşlev(int değer)
    {
        değişken = değer;
    }
}

O yapının üye işlevi, değişken isminde bir değişkene bir atama yapmaktadır. O değişkenin programdaki hangi değişken olduğu; bu şablon tanımlandığı zaman değil, şablon kullanıldığı zaman belirlenir:

    int x = 1;
    int y = 2;

    auto nesne = BirYapı!x();
    nesne.birİşlev(10);
    writeln("x: ", x, ", y: ", y);

Yapı şablonunun yukarıdaki kullanımında yerel x değişkeni belirtildiği için, birİşlev içindeki atama, onu etkiler:

x: 10, y: 2

Şablon parametresi olarak y belirtildiğinde de o değişken değişir:

    auto nesne = BirYapı!y();
    nesne.birİşlev(10);
    writeln("x: ", x, ", y: ", y);

Bu sefer y değişmiştir:

x: 1, y: 10

Başka bir örnek olarak, alias parametresini işlev olarak kullanan bir işlev şablonuna bakalım:

void çağıran(alias işlev)()
{
    write("çağırıyorum: ");
    işlev();
}

() parantezlerinden anlaşıldığı gibi, çağıran ismindeki işlev şablonu, kendisine verilen parametreyi bir işlev olarak kullanmaktadır. Ayrıca; parantezlerin içinin boş olmasından anlaşıldığı gibi, o işlev parametre göndermeden çağrılmaktadır.

Parametre almadıkları için o kullanıma uyan iki de işlev bulunduğunu varsayalım:

void birinci()
{
    writeln("birinci");
}

void ikinci()
{
    writeln("ikinci");
}

O işlevler, çağıran şablonu içindeki kullanıma uydukları için o şablonun alias parametresinin değeri olabilirler:

    çağıran!birinci();
    çağıran!ikinci();

Belirtilen işlevin çağrıldığını görürüz:

çağırıyorum: birinci
çağırıyorum: ikinci

alias şablon parametrelerini her çeşit şablonla kullanabilirsiniz. Önemli olan, o parametrenin şablon içindeki kullanıma uymasıdır. Örneğin, yukarıdaki alias parametresi yerine bir değişken kullanılması derleme hatasına neden olacaktır:

    int değişken;
    çağıran!değişken();        // ← derleme HATASI

Aldığımız hata, () karakterlerinden önce bir işlev beklendiğini, int türündeki değişken'in uygun olmadığını belirtir:

Error: function expected before (), not değişken of type int

Her ne kadar işaretlediğim satır nedeniyle olsa da, derleme hatası aslında çağıran işlevinin içindeki işlev() satırı için verilir. Derleyicinin gözünde hatalı olan; şablona gönderilen parametre değil, o parametrenin şablondaki kullanılışıdır.

Uygunsuz şablon parametrelerini önlemenin bir yolu, şablon kısıtlamaları tanımlamaktır. Bunu aşağıda göreceğiz.

Çokuzlu parametreleri

İşlevlerin belirsiz sayıda parametre alacak şekilde tanımlanabildiklerini biliyoruz. Örneğin writeln işlevini istediğimiz sayıda parametre ile çağırabiliriz. Bu tür işlevlerin nasıl tanımlandıklarını Parametre Serbestliği dersinde görmüştük.

Aynı serbestlik şablon parametrelerinde de bulunur. Şablon parametrelerinin sayısını ve çeşitlerini serbest bırakmak, şablon parametre listesinin en sonuna bir çokuzlu ismi ve ... karakterleri yazmak kadar basittir.

Bunun bir örneğini, bir işlev şablonunda görelim:

void bilgiVer(alias işlev, Çokuzlu ...)(Çokuzlu parametreler)
{
    // ...
}

Yukarıdaki işlev şablonunun ilk parametresini alias olarak seçtim. Onun yerine başka çeşit bir şablon parametresi de olabilirdi, ve yanında başka şablon parametreleri de bulunabilirdi.

İsminden anlaşıldığı gibi, o alias parametresinin işlev olarak kullanılacağı düşünülmektedir.

O parametreden sonra yazılan Çokuzlu ..., bilgiVer işlev şablonunun belirsiz sayıda parametre ile çağrılabilmesini sağlar. O çokuzlu, belirsiz işlev parametrelerinin hepsini birden içerir. Örneğin o işlev şablonunun şu şekilde çağrıldığını düşünelim:

    bilgiVer!writeln(1, "abc", 2.3);

Şablona açıkça verilen writeln parametresi, şablonun tanımında kullanılan alias'ın değeri olur. İşleve gönderilen bütün parametreler de parametreler isimli çokuzluyu oluştururlar.

O çokuzlunun parçalarına da şablon içinde örneğin bir foreach ile erişilebilir:

void bilgiVer(alias işlev, Çokuzlu ...)(Çokuzlu parametreler)
{
    foreach (i, parametre; parametreler) {
        işlev(i, ": ", typeid(parametre),
              " türünde ", parametre);
    }
}

Çıktısı:

0: int türünde 1
1: immutable(char)[] türünde abc
2: double türünde 2.3

alias olan ilk şablon parametresi, bilgiVer'in kendi işini yaparken hangi işlevden yararlanacağını belirtmektedir. Örneği devam ettirmek için, writeln yerine kendi yazdığımız bir işlevden yararlanalım.

işlev'in şablon içinde beş parametre ile çağrıldığını görüyoruz. O kullanıma uyduğu sürece, alias parametresi olarak herhangi bir işlevi belirtebiliriz. O beş parametreden bazılarını gözardı eden, ve yalnızca türü ve değeri küme parantezleri arasında yazdıran şu işlev şablonuna bakalım:

void yalnızcaTürVeDeğer(TürBilgisi, Tür)(int sayaç,
                                         string dizgi1,
                                         TürBilgisi türİsmi,
                                         string dizgi2,
                                         Tür değer)
{
    write('{', türİsmi, ' ', değer, '}');
}

bilgiVer içindeki işlev çağrısına uyduğu için, bilgiVer'i bu yeni işlevle de kullanabiliriz:

    bilgiVer!yalnızcaTürVeDeğer(1, "abc", 2.3);

Sonuçta da o üç parametrenin yalnızca türlerini ve değerlerini küme parantezleri arasında elde ederiz:

{int 1}
{immutable(char)[] abc}
{double 2.3}

Aynı amaca ulaşmak için başka yollar bulunduğunu biliyorsunuz. Örneğin bilgiVer yerine türVeDeğerBildir gibi daha kısa bir şablon da yazabilirdik. Ben, çokuzlu parametrelerinden önceki parametrelerin nasıl kullanıldıklarını göstermek için bir alias parametresi seçtim.

Şablon özellemeleri

Özellemeleri de Şablonlar dersinde anlatmıştım. Aşağıdaki meta programlama başlığında da özelleme örnekleri göreceksiniz.

Tür parametrelerinde olduğu gibi, başka çeşit şablon parametreleri de özellenebilir. Örneğin değer parametreleri:

void birİşlev(int birDeğer)()
{
    // ... genel tanımı ...
}

void birİşlev(int birDeğer : 0)()
{
    // ... sıfıra özel tanımı ...
}
Meta programlama

Kod üretme ile ilgili olmaları nedeniyle, şablonlar diğer D olanaklarından daha üst düzeyde programlama araçları olarak kabul edilirler. Şablonlar bir anlamda, kod oluşturan kodlardır. Kodların daha üst düzey kodlarla oluşturulmaları kavramına meta programlama denir.

Şablonların derleme zamanı olanakları olmaları, normalde çalışma zamanında yapılan işlemlerin derleme zamanına taşınmalarına olanak verir. (Not: Aynı amaçla Derleme Zamanında İşlev İşletme (CTFE) olanağı da kullanılabilir. Bu konuyu daha sonraki bir derste göstereceğim.)

Şablonların bu amaçla derleme zamanında işletilmeleri, çoğunlukla özyineleme üzerine kuruludur.

Bunun bir örneğini görmek için 0'dan başlayarak belirli bir sayıya kadar olan bütün sayıların toplamını hesaplayan normal bir işlev düşünelim. Bu işlev, parametre olarak örneğin 4 aldığında 0+1+2+3+4'ün toplamını döndürsün:

int topla(int sonDeğer)
{
    int sonuç;

    foreach (değer; 0 .. sonDeğer + 1) {
        sonuç += değer;
    }

    return sonuç;
}

Aynı işlevi özyinelemeli olarak da yazabiliriz:

int topla(int sonDeğer)
{
    return (sonDeğer == 0
            ? sonDeğer
            : sonDeğer + topla(sonDeğer - 1));
}

Özyinelemeli işlev; kendi düzeyindeki değeri, bir eksik değerli hesaba eklemektedir. İşlevde 0 değerinin özel olarak kullanıldığını görüyorsunuz; özyineleme zaten onun sayesinde sonlanmaktadır.

İşlevlerin normalde çalışma zamanı olanakları olduklarını biliyoruz. topla'yı çalışma zamanında gerektikçe çağırabilir ve sonucunu kullanabiliriz:

    writeln(topla(4));

Aynı sonucun yalnızca derleme zamanında gerektiği durumlarda ise, o hesap bir işlev şablonuyla da gerçekleştirilebilir. Yapılması gereken; değerin işlev parametresi olarak değil, şablon parametresi olarak kullanılmasıdır:

// Uyarı: Bu kod yanlıştır
int topla(int sonDeğer)()
{
    return (sonDeğer == 0
            ? sonDeğer
            : sonDeğer + topla!(sonDeğer - 1)());
}

Bu şablon da hesap sırasında kendisinden yararlanmaktadır. Kendisini, sonDeğer'in bir eksiği ile kullanmakta ve hesabı yine özyinelemeli olarak elde etmeye çalışmaktadır. Ne yazık ki o kod yazıldığı şekilde çalışamaz.

Derleyici, ?: işlecini çalışma zamanında işleteceği için, yukarıdaki özyineleme derleme zamanında sonlanamaz:

    writeln(topla!4());    // ← derleme HATASI

Derleyici, aynı şablonun sonsuz kere dallandığını anlar ve bir hata ile sonlanır:

Error: template instance deneme.topla!(-296) recursive expansion

Şablon parametresi olarak verdiğimiz 4'ten geriye doğru -296'ya kadar saydığına bakılırsa, derleyici şablonların özyineleme sayısını 300 ile sınırlamaktadır.

Meta programlamada özyinelemeyi kırmanın yolu, şablon özellemeleri kullanmaktır. Bu durumda, aynı şablonu 0 değeri için özelleyebilir ve özyinelemenin kırılmasını bu sayede sağlayabiliriz:

// Sıfırdan farklı değerler için kullanılan tanım
int topla(int sonDeğer)()
{
    return sonDeğer + topla!(sonDeğer - 1)();
}

// Sıfır değeri için özellemesi
int topla(int sonDeğer : 0)()
{
    return 0;
}

Derleyici, sonDeğer'in sıfırdan farklı değerleri için hep genel tanımı kullanır ve en sonunda 0 değeri için özel tanıma geçer. O tanım da basitçe 0 değerini döndürdüğü için özyineleme sonlanmış olur.

O işlev şablonunu şöyle bir programla deneyebiliriz:

import std.stdio;

int topla(int sonDeğer)()
{
    return sonDeğer + topla!(sonDeğer - 1)();
}

int topla(int sonDeğer : 0)()
{
    return 0;
}

void main()
{
    writeln(topla!4());
}

Şimdi hatasız olarak derlenecek ve 4+3+2+1+0'ın toplamını üretecektir:

10

Burada dikkatinizi çekmek istediğim önemli nokta, topla!4() işlevinin bütünüyle derleme zamanında işletiliyor olmasıdır. Sonuçta derleyicinin ürettiği kod, writeln'e doğrudan 10 hazır değerini göndermenin eşdeğeridir:

    writeln(10);         // topla!4()'lü kodun eşdeğeri

Derleyicinin ürettiği kod, 10 hazır değerini doğrudan programa yazmak kadar hızlı ve basittir. O 10 hazır değeri, yine de 4+3+2+1+0 hesabının sonucu olarak bulunmaktadır; ancak o hesap, şablonların özyinelemeli olarak kullanılmalarının sonucunda derleme zamanında işletilmektedir.

Burada görüldüğü gibi, meta programlamanın yararlarından birisi, şablonların derleme zamanında işletilmelerinden yararlanarak normalde çalışma zamanında yapılmasına alıştığımız hesapların derleme zamanına taşınabilmesidir.

Yukarıda da söylediğim gibi, daha sonraki bir derste göstereceğim CTFE olanağı, bazı meta programlama yöntemlerini D'de gereksiz hale getirir.

Derleme zamanı çok şekilliliği

Bu kavram, İngilizce'de "compile time polymorphism" olarak geçer.

Nesne yönelimli programlamada çok şekilliliğin sınıf türetme ile sağlandığını biliyorsunuz. Örneğin bir işlev parametresinin bir arayüz olması, o parametre yerine o arayüzden türemiş her sınıfın kullanılabileceği anlamına gelir.

Daha önce gördüğümüz bir örneği hatırlayalım:

import std.stdio;

interface SesliAlet
{
    string ses();
}

class Keman : SesliAlet
{
    string ses()
    {
        return "♩♪♪";
    }
}

class Çan : SesliAlet
{
    string ses()
    {
        return "çın";
    }
}

void sesliAletKullan(SesliAlet alet)
{
    // ... bazı işlemler ...
    writeln(alet.ses());
    // ... başka işlemler ...
}

void main()
{
    sesliAletKullan(new Keman);
    sesliAletKullan(new Çan);
}

Yukarıdaki sesliAletKullan işlevi çok şekillilikten yararlanmaktadır. Parametresi SesliAlet olduğu için, ondan türemiş olan bütün türlerle kullanılabilir.

Yukarıdaki son cümlede geçen bütün türlerle kullanılabilme kavramını şablonlardan da tanıyoruz. Böyle düşününce, şablonların da bir çeşit çok şekillilik sunduklarını görürüz. Şablonlar bütünüyle derleyicinin derleme zamanındaki kod üretmesiyle ilgili olduklarından, şablonların sundukları çok şekilliliğe derleme zamanı çok şekilliliği denir.

Doğrusu, her iki çok şekillilik de bütün türlerle kullanılamaz. Her ikisinde de türlerin uymaları gereken bazı koşullar vardır.

Çalışma zamanı çok şekilliliği, belirli bir arayüzden türeme ile kısıtlıdır.

Derleme zamanı çok şekilliliği ise şablon içindeki kullanıma uyma ile kısıtlıdır. Şablon parametresi, şablon içindeki kullanımda derleme hatasına neden olmuyorsa, o şablonla kullanılabilir. (Not: Eğer tanımlanmışsa, şablon kısıtlamalarına da uyması gerekir. Şablon kısıtlamalarını biraz aşağıda anlatacağım.)

Örneğin, yukarıdaki sesliAletKullan işlevi bir şablon olarak yazıldığında, ses üye işlevi bulunan bütün türlerle kullanılabilir:

void sesliAletKullan(T)(T alet)
{
    // ... bazı işlemler ...
    writeln(alet.ses());
    // ... başka işlemler ...
}

class Araba
{
    string ses()
    {
        return "düt düt";
    }
}

// ...

    sesliAletKullan(new Keman);
    sesliAletKullan(new Çan);
    sesliAletKullan(new Araba);

Yukarıdaki şablon, diğerleriyle kalıtım ilişkisi bulunmayan Araba türü ile de kullanılabilmiştir.

Kod şişmesi

Şablonlar kod üretme ile ilgilidirler. Derleyici, şablonun farklı parametrelerle her kullanımı için farklı kod üretir.

Örneğin yukarıda en son yazdığımız sesliAletKullan işlev şablonu, programda kullanıldığı her tür için ayrı ayrı üretilir ve derlenir. Programda yüz farklı tür ile çağrıldığını düşünürsek; derleyici o işlev şablonunun tanımını, her tür için ayrı ayrı, toplam yüz kere oluşturacaktır.

Programın boyutunun büyümesine neden olduğu için bu etkiye kod şişmesi (code bloat) denir. Çoğu programda sorun oluşturmasa da, şablonların bu özelliğinin akılda tutulması gerekir.

Öte yandan, sesliAletKullan işlevinin ilk yazdığımız SesliAlet alan tanımında, yani şablon olmayan tanımında, böyle bir kod tekrarı yoktur. Derleyici, o işlevi bir kere derler ve her SesliAlet türü için aynı işlevi çağırır. İşlev tek olduğu halde her hayvanın kendisine özel olarak davranabilmesi, derleyici tarafından işlev göstergeleriyle sağlanır. Derleyici her tür için farklı bir işlev göstergesi kullanır ve böylece her tür için farklı üye işlev çağrılır. Çalışma zamanında çok küçük bir hız kaybına yol açsa da, işlev göstergeleri kullanmanın çoğu programda önemi yoktur ve zaten bu çözümü sunan en hızlı gerçekleştirmedir.

Burada sözünü ettiğim hız etkilerini tasarımlarınızda fazla ön planda tutmayın. Program boyutunun artması da, çalışma zamanında fazladan işlemler yapılması da hızı düşürecektir. Belirli bir programda hangisinin etkisinin daha fazla olduğuna ancak o program denenerek karar verilebilir.

Şablon kısıtlamaları

Şablonların her tür ve değerdeki şablon parametresi ile çağrılabiliyor olmalarının getirdiği bir sorun vardır. Uyumsuz bir parametre kullanıldığında, bu uyumsuzluk ancak şablonun kendi kodları derlenirken farkedilebilir. Bu yüzden, derleme hatasında belirtilen satır numarası, şablon bloğuna işaret eder.

Yukarıdaki sesliAletKullan şablonunu ses isminde üye işlevi bulunmayan bir türle çağıralım:

class Fincan
{
    // ... ses() işlevi yok ...
}

// ...

    sesliAletKullan(new Fincan);   // ← uyumsuz bir tür

Oradaki hata, şablonun uyumsuz bir türle çağrılıyor olmasıdır. Oysa derleme hatası, şablon içindeki kullanıma işaret eder:

void sesliAletKullan(T)(T alet)
{
    // ... bazı işlemler ...
    writeln(alet.ses());          // ← derleme HATASI
    // ... başka işlemler ...
}

Bunun bir sakıncası, belki de bir kütüphane modülünde tanımlı olan bir şablona işaret edilmesinin, hatanın o kütüphanede olduğu yanılgısını uyandırabileceğidir. Daha önemlisi, asıl hatanın hangi satırda olduğunun hiç bildirilmiyor olmasıdır.

Böyle bir sorunun arayüzlerde bulunmadığına dikkat edin. Parametre olarak arayüz alacak şekilde yazılmış olan bir işlev, ancak o arayüzden türemiş olan türlerle çağrılabilir. Türeyen her tür arayüzün işlevlerini gerçekleştirmek zorunda olduğu için, işlevin uyumsuz bir türle çağrılması olanaksızdır. O durumda derleme hatası, işlevi uygunsuz türle çağıran satıra işaret eder.

Şablonların yalnızca belirli koşulları sağlayan türlerle kullanılmaları şablon kısıtlamaları ile sağlanır. Şablon kısıtlamaları, şablon bloğunun hemen öncesine yazılan if deyiminin içindeki mantıksal ifadelerdir:

void birŞablon(T)()
    if (/* ... kısıtlama koşulu ... */)
{
    // ... şablonun tanımı ...
}

Derleyici bu şablon tanımını ancak kısıtlama koşulu true olduğunda göze alır. Koşulun false olduğu durumda ise bu şablon tanımını gözardı eder.

Bunun bir örneği olarak meta programlama yoluyla faktöriyel hesaplayan bir şablona ve onun 0 değeri için özellemesine bakalım:

import std.stdio;

ulong faktöriyel(ulong değer)()
{
    return değer * faktöriyel!(değer - 1)();
}

ulong faktöriyel(ulong değer : 0)()
{
    return 1;
}

void main()
{
    writefln("19! == %25s", faktöriyel!19());
    writefln("20! == %25s", faktöriyel!20());
    writefln("21! == %25s", faktöriyel!21());
    writeln();
    writefln("(ulong.max: %s)", ulong.max);
    writefln("(doğru 21!: 51090942171709440000)");
}

Yukarıdaki program; 19, 20, ve 21 değerlerinin faktöriyelini hesaplamaktadır. (Not: Aynı işin şablon kullanmadan, normal işlevlerle de gerçekleştirilebileceğini hatırlatmak istiyorum. Yukarıdaki şablonlar, sonuçların derleme zamanında hesaplanmalarını ve sanki kaynak koda örneğin doğrudan 121645100408832000 yazılmış gibi derlenmelerini sağlarlar.)

O programdaki değerleri özel olarak seçtim; çünkü 21'in faktöriyelinin ulong'a sığmadığını biliyorum. Yukarıdaki kod, 21 ve daha büyük değerlerin faktöriyelini yanlış hesaplar:

19! ==        121645100408832000
20! ==       2432902008176640000
21! ==      14197454024290336768

(ulong.max: 18446744073709551615)
(doğru 21!: 51090942171709440000)

21'in faktöriyeli, 51,090,942,171,709,440,000 değeridir. O değer ulong'a sığmadığı için taşmıştır.

faktöriyel şablonunun geçersiz değerlerle kullanılması ve bu yüzden yanlış sonuçlar üretmesi bir şablon kısıtlaması ile engellenebilir:

ulong faktöriyel(ulong değer)()
    if (değer <= 20)
{
    return değer * faktöriyel!(değer - 1)();
}

Derleyici, şablonu ancak o koşula uyan değerler için göze alacaktır:

  writefln("21! == %25s", faktöriyel!21()); // ← derleme HATASI

Derleme hatası, o kullanıma karşılık bir şablon tanımı bulunmadığını bildirir:

Error: template instance faktöriyel!(21) does not match any
template declaration

Böylece amacımıza ulaşmış oluruz. Artık derleme hatası şablonun yanlış parametre ile kullanıldığı satıra işaret etmektedir.

Şablonlar derleme zamanı olanakları oldukları için, şablon kısıtlamaları da derleme zamanında işletilirler. Bu yüzden, Koşullu Derleme dersinde gördüğümüz ve derleme zamanında işletildiğini öğrendiğimiz is ifadesi ile de çok kullanılırlar. Bunun örneklerini aşağıda göstereceğim.

İsimli kısıtlama yöntemi

Şablon kısıtlamaları bazı durumlarda yukarıdakinden çok daha karmaşık olabilirler. Bunun üstesinden gelmenin bir yolu, benim isimli kısıtlama olarak adlandırdığım bir yöntemdir. Bu yöntem D'nin dört olanağından yararlanarak kısıtlamaya anlaşılır bir isim verir. Bu dört olanak; isimsiz kapama, typeof, is ifadesi, ve tek tanım içeren şablonlardır.

Bu yöntemi burada daha çok bir kalıp olarak göstereceğim ve her ayrıntısına girmemeye çalışacağım.

Parametresini belirli şekilde kullanan bir işlev şablonu olsun:

void kullan(T)(T nesne)
{
    // ...
    nesne.hazırlan();
    // ...
    nesne.uç(42);
    // ...
    nesne.kon();
    // ...
}

Şablon içindeki kullanımından anlaşıldığı gibi, bu şablonun kullanıldığı türlerin hazırlan, , ve kon isminde üç üye işlevinin bulunması gerekir. O işlevlerden 'un ayrıca int türünde bir de parametresi olmalıdır.

Bu kısıtlamayı is ve typeof ifadelerinden yararlanarak şöyle yazabiliriz:

void kullan(T)(T nesne)
    if (is (typeof(T.hazırlan)) &&
        is (typeof(T.uç(1))) &&
        is (typeof(T.kon)))
{
    // ...
}

O tanımı aşağıda biraz daha açmaya çalışacağım. Şimdilik, is (typeof(T.hazırlan)) kullanımını bir kalıp olarak "eğer o türün hazırlan isminde bir üye işlevi varsa" diye kabul edebilirsiniz. İşleve is (typeof(T.uç(1))) şeklinde bir de parametre verildiğinde ise, "o işlev ek olarak o türden bir parametre de alıyorsa" diye kabul edebilirsiniz.

Yukarıdaki gibi bir kısıtlama istendiği gibi çalışıyor olsa da, her zaman için tam açık olmayabilir. Onun yerine, o şablon kısıtlamasının ne anlama geldiğini daha iyi açıklayan bir isim verilebilir:

void kullan(T)(T nesne)
    if (uçabilir_mi!T)      // ← isimli kısıtlama
{
    // ...
}

Bu kısıtlama bir öncekinden daha açıktır. Bu şablonun uçabilen türlerle çalıştığını okunaklı bir şekilde belgeler.

Yukarıdaki gibi isimli kısıtlamalar şu kalıba uygun olarak tanımlanırlar:

template uçabilir_mi(T)
{
    const bool uçabilir_mi = is (typeof(
    {
        T uçan;
        uçan.hazırlan();   // uçmaya hazırlanabilmeli
        uçan.uç(1);        // belirli mesafe uçabilmeli
        uçan.kon();        // istendiğinde konabilmeli
    }()));
}

O yöntemde kullanılan D olanaklarını ve birbirleriyle nasıl etkileştiklerini çok kısaca göstermek istiyorum:

template uçabilir_mi(T)
{
    //            (6)        (5)  (4)
    const bool uçabilir_mi = is (typeof(
    { // (1)
        T uçan;          // (2)
        uçan.hazırlan();
        uçan.uç(1);
        uçan.kon();
 // (3)
    }()));
}
  1. İsimsiz kapama: İsimsiz kapamaları İşlev Göstergeleri ve Kapamalar dersinde görmüştük. Yukarıdaki küme parantezleri, isimsiz bir kapama tanımlar.
  2. Kapama bloğu: Kapama bloğu, kısıtlaması tanımlanmakta olan türü, asıl şablonda kullanıldığı gibi kullanır. Yukarıdaki blokta önce bu türden bir nesne oluşturulmakta, ve o türün sahip olması gereken üç üye işlevi çağrılmaktadır. (Not: Bu kodlar typeof tarafından kullanılırlar ama hiçbir zaman işletilmezler.)
  3. Kapamanın işletilmesi: Bir kapamanın sonuna yazılan () parantezleri, normalde o kapamayı işletir. Ancak, yukarıdaki işletme bir typeof içinde olduğu için, bu kapama hiçbir zaman işletilmez.
  4. typeof ifadesi: typeof, şimdiye kadarki örneklerde çok kullandığımız gibi, kendisine verilen ifadenin türünü üretir.
  5. typeof'un önemli bir özelliği, türünü ürettiği ifadeyi hiç bir şekilde işletmemesidir. typeof, bir ifadenin eğer işletilse ne türden bir değer üreteceğini bildirir:

        int i = 42;
        typeof(++i) j;    // 'int j;' yazmakla aynı şey
    
        assert(i == 42);  // ++i işletilmemiştir
    

    Yukarıdaki assert'ten de anlaşıldığı gibi, ++i ifadesi işletilmemiştir. typeof, yalnızca o ifadenin türünü üretmiş ve böylece j de int olarak tanımlanmıştır.

    Eğer typeof'a verilen ifadenin geçerli bir türü yoksa, typeof void bile olmayan geçersiz bir tür döndürür.

    Eğer uçabilir_mi şablonuna gönderilen tür, o isimsiz kapama içindeki kodlarda gösterildiği gibi derlenebiliyorsa, typeof geçerli bir tür üretir. Eğer o tür kapama içindeki kodlardaki gibi derlenemiyorsa, typeof geçersiz bir tür döndürür.

  6. is ifadesi: Koşullu Derleme dersinde is ifadesinin birden fazla kullanımını görmüştük. Buradaki is (Tür) şeklindeki kullanımı, kendisine verilen türün anlamlı olduğu durumda true değerini üretiyordu:
  7.     int i;
        writeln(is (typeof(i)));                  // true
        writeln(is (typeof(varOlmayanBirİsim)));  // false
    

    Yukarıdaki ikinci ifadede bilinmeyen bir isim kullanıldığı halde, derleyici hata vermez. Programın çıktısı ikinci satır için false değerini içerir:

    true
    false
    

    Bunun nedeni, typeof'un ikinci kullanım için geçersiz bir tür üretmiş olmasıdır.

  8. Tek tanım içeren şablon: Bu derste anlatıldığı gibi, uçabilir_mi şablonunun içinde tek tanım bulunduğu için, ve o tanımın ismi şablonun ismiyle aynı olduğu için; bu şablon, içindeki tek tanım yerine geçebilir.

İşte, yukarıdaki kullan işlev şablonunun kısıtlaması, bütün bu olanaklar sayesinde kullanışlı bir isim edinmiş olur:

void kullan(T)(T nesne)
    if (uçabilir_mi!T)
{
    // ...
}

O şablonu birisi uyumlu, diğeri uyumsuz iki türle çağırmayı deneyelim:

// Şablondaki kullanıma uyan bir tür
class ModelUçak
{
    void hazırlan()
    {}

    void uç(int mesafe)
    {}

    void kon()
    {}
}

// Şablondaki kullanıma uymayan bir tür
class Güvercin
{
    void uç(int mesafe)
    {}
}

// ...

    kullan(new ModelUçak);      // ← derlenir
    kullan(new Güvercin);       // ← derleme HATASI

İsimli veya isimsiz, bir şablon kısıtlaması tanımlanmış olduğu için; bu derleme hatası artık şablonun içine değil, şablonun uyumsuz türle kullanıldığı satıra işaret eder.

Özet

Önceki şablonlar dersinin sonunda şunları hatırlatmıştım:

Bu derste de şu kavramları gördük: