şablonların özelleştirilmesi (template specialization)

Bir sınıf şablonu ya da bir işlev şablonundan kod üretimi her tür için uygun olmayabilir. Bazı türlerin arayüzü şablon kodda uygulanmış işlemleri desteklemeyebilir. Bazı türler için de şablon koddaki işlemleri kullanmak yerine bu türlere ait özel avantajlardan faydalanmak isteyebiliriz.

C++ dili şablonlarda bazı seçilmiş türler için alternatif başka şablonların kullanılabilmesini sağlayan “şablonların özelleştirilmesi “  denen bir mekanizmaya sahiptir. (Burada standartlar İngilizce explicit specialization” terimini kullanmıştır). Özelleştirme, bir ya da birden fazla şablon parametresinin belirtilen türlerden olması durumunda kullanılacak olan başka bir kod tanımıdır.
Basit bir örnekle başlayalım. İki değerden büyük olanı bulmaya yönelik işlevlerin yazılabilmesi için aşağıdaki gibi bir işlev şablonu yazıldığını düşünelim:

T türünün const char * türü olması durumunda derleyicinin yukarıdaki şablondan üreteceği kod bizim için doğru işi yapmayacak. Çünkü derleyicinin ürettiği kodda büyüklük karşılaştırılmasına sokulan yazılar değil adresler olacak. Yani işlem bu durumda büyük olan yazıya değil büyük olan adrese geri dönecek. Şimdi const char * türü için bu şablonu özelleştirelim. Özel bir sentaks ile const char * türü için alternatif bir kod sunuyoruz:

Sentaksa dikkat edelim: template anahtar sözcüğünü izleyen içi boş açısal parantez, bu kodun bir özelleştirme olduğunu anlatıyor. getmax ismini izleyen açısal parantezin içindeki tür bilgisi ise özelleştirmenin hangi türe ilişkin olduğunu belirtiyor. Bu tanımın yapıldığı kaynak kod noktasında getmax isimli şablon görünür durumda değilse derleyici sözdizim hatası verecektir.
Eğer getmax işlevine gönderilen argümanlar const char * türünden olursa ya da şablonun açıkça const char * türü için açılması istenirse bu özelleştirilmiş kod kullanılır.

Bir işlev şablonunun belirli bir tür için özelleştirmesi durumunda birincil şablonun işlev parametrelerinin yapısı (referans kullanılıp kullanılmadığı, referansın const olup olmadığı gibi) özelleştirmede korunmak zorunda. Aşağıdaki şablona bakalım:

Yukarıdaki şablonda, T şablon tür parametresi olmak üzere getmax işlev şavlonunun parametre değişkenleri const T & olarak tanımlanmış. Şimdi bu şablonu const char * türü için özelleştirmek isteyelim. Bu durumda T türü olarak const char * türü seçildiğinden özelleştirilmiş işlevin parametre değişkenleri const char * const & olarak tanımlanmalı:

İşlev şablonlarının özelleştirilmesi (specialization) ile yüklenmesi (overloading) farklı araçlar. Bir işlev şablonu yüklenebilir; yani aynı isimli fakat imzaları farklı birden fazla işlev şablonu aynı kapsamda bir arada bulunabilir. Bu durumda işlev şablonlarına bir çağrı yapıldığında eğer birden fazla şablon çağrıya uygun düşüyorsa kod üretimi için daha spesifik olan şablon seçilir. Şablonlardan herhangi biri için yapılan özelleştirme hangi işlevin çağrıldığı sürecine (function overload resolution) dahil edilmez.  Aşağıdaki kodu inceleyelim:

Yukarıdaki kodda func isimli iki ayrı işlev şablonu var. Şablonlardan birincisinin tek şablon tür parametresi varken diğerinin iki şablon tür parametresi var. Tek şablon tür parametreli func şablonu std:pair<int int> türü için özelleştirilmiş. main işlevi içinde std::pair<int, int> türünden tanımlanan ip isimli nesne func işlevine arguman olarak gönderiliyor. Tek şablon tür parametreli şablondan bu tür için bir özelleştirilmiş kod tanımı olmasına karşın bu işlev, hangi şablondan kodun üretileceği konusunda dikkate alınmayacak. Her iki şablon da işlev çağrısına uygun olmasına karşın ikinci şablon söz konusu işlev çağrısı için daha spesifik bir yapıya sahip olduğundan derleyici kod üretimini ikinci şablondan yapacak.

Oysa özelleştirme yapmak yerine gerçek bir işlev tanımlansaydı durum değişik olurdu. Gerçek işlevler hangi işlevin çağrıldığının anlaşılması sürecine dahil edilirler. Gerçek bir işlevin parametre değişkenlerinin gönderilen arguman ifadelerle tam tür uyumu içinde olması durumunda, işlev şablonlarından üretilecek işlevlere göre üstünlüğü vardır. Yukarıdaki kodda bir değişiklik yapıyoruz:

Yukarıdaki kodda çağrılan std::pair<int, int> parametreli gerçek işlev olacak. Bu işlevin parametre değişkeni, işleve arguman olarak gönderilen ifadenin türüyle tamamen aynı türde.

sınıf şablonlarının özelleştirilmesi

Sınıf şablonları için de özelleştirme kullanılabilir.  Aşağıdaki koda bakalım:

Yukarıdaki kodda Myclass isimli bir sınıf şablonu double türü için özelleştiriliyor.  double türü dışındaki tüm türler için derleyici birincil şablonu (primary template) kullanırken double türü için  özelleştirime işlemiyle sunulan koddan üretim yapacak.

Belirli bir tür için yapılan özelleştirmenin birincil şablonda sunulan aynı arayüze sahip olması zorunlu değildir. Özelleştirme için tamamen farklı bir arayüz bile sunulabilir. Örneğin standart kütüphanenin vector sınıf şablonu  bool türü için özelleştirilmiştir ve bu özelleştirme farklı bir arayüze sahiptir.

Özelleştirme değer (non type) parametreler için de yapılabilir:

kısmi özelleştirme

Özelleştirmenin ikinci biçimi kısmi özelleştirmedir (partial specialization).  Kısmi özelleştirme  belirli bir tür için değil belirli özelliğe sahip bir tür grubu için yapılabilir.  Kısmi özelleştirme için birincil şablonun tür alanı daraltılmalıdır. Örneğin gösterici türleri ya da referans türleri için kısmi özelleştirme yapılabilir. Kısmi özelleştirme yalnızca sınıf şablonlarına tanınan bir özelliktir ve işlev şablonlarında kullanılamaz.

Yukarıdaki kodda tanımlanan Myclass isimli sınıf şablonu gösterici türleri için kısmi özelleştirme işlemine tabi turuluyor. Yani bu sınıf şablonundan bir gösterici türü için sınıfı kodu üretilmesi durumunda kısmi özelleştirmeye ilişkin kod kullanılır. main işlevi içinde tanımlanan x ve y nesneleri için derleyici sınıf kodlarını birincil şablondan üretirken z nesnesi için gereken sınıf kodunu kısmi özelleştirme kodundan üretir.

Kısmi özelleştirmeye standart kütüphaneden bir örnek verelim. iterator başlık dosyasında bildirilen std::iterator_traits sınıfı gösterici türleri ve const gösterici türleri için özelleştirilmiştir.

Kısmi özelleştirme daha spesifik bir tür grubu için de yapılabilir. Eğer bir sınıf için birden fazla özelleştirme kullanılabilir durumda ise derleyici uygun olan özelleştirmelerden en fazla daraltılmış olanı seçer:

Yukarıdaki kodda iki şablon tür parametreli Myclass sınıf şablonu için iki ayrı kısmi özelleştirme uygulanmış. Birinci kısmi özelleştirme her iki şablon tür parametresinin de aynı türden olması durumu için belirlenmiş. İkinci kısmi özelleştirme ise her iki şablon tür parametresinin aynı türden gösterici olması durumu için gerçekleştirilmiş.
main işlevi içinde  tanımlanmış sınıf nesneleri için derleyici hangi şablonlarını kullanacak?
m1 nesnesinin türü iki özelleştirmeye de uymuyor. Derleyici m1 nesnesinin ait olduğu sınıf için birincil şablonu kullanacak. m2 nesnesinin tanımında her iki şablon tür parametresi için double türü kullanılmış. Bu durum yalnızca birinci özelleştirmeye uyduğu için bu özelleştirme kullanılacak.
m3 nesnesi için her iki şablon tür parametresi de int * türü kullanılmış. Hem birinci  hem de ikinci kısmi özelleştirme de duruma uygun. Bu durumda  derleyici daha daraltılmış olan ikinci özelleştirmeyi kullanacak.

Kısmi özelleştirmeye konu şablonda kullanılacak şablon tür parametresi sayısı birincil şablonda kullanılan şablon tür parametre sayısından daha fazla olabilir. Önemli olan kısmi özelleştirme ile hitap edilen tür alanının daraltılmasıdır. Aşağıdaki örneğe bakınız:

Yukarıdaki kodda tanımlanan Tclass isimli sınıf şablonunun tek şablon parametresi olduğunu görüyorsunuz. Bu şablondan std::pair türleri için yapılan özelleştirmede iki şablon tür parametresi kullanılıyor. Sonuçta daha spesifik bir tür grubu söz konusu olduğundan özelleştirme geçerlidir.

Şablon meta programlamada özelleştirme çoğu zaman özyinelemeli bir şablon üretimini sonlandıracak ana durum (base case) olarak kullanılır. Aşağıdaki kodu inceleyin:

Fact<5> için yapılan açılımla derleyici sırasıyla Fact<4>, Fact<3>, Fact<2 > ve Fact<1> türlerinin  kodunu yazacak. Sıra Fact<0>‘ın kodunun yazılmasına gelince derleyici özelleştirilmiş kodu kullanacak. Böylece derleme zamanında kullanılmış olan özyinelemeli yapı durdurulmuş olacak.

Yazımızı iki ayrı çalışma sorusu ile sonlandıralım.

Birinci çalışma sorusu:

a ve b sabit ifadeleri olmak üzere Power<a, b>::value değerinin bir derleme zamanı sabit ifadesi olmak üzere a‘nın b. kuvveti değerinde olmasını sağlayacak uygun sınıf şablonlarını yazınız.

İkinci çalışma sorusu:

Altın oranın hesaplanmasında kullanılan Fibonacci serisi

biçimindedir. Görüldüğü gibi serinin n. terimi serinin (n-1). ve (n – 2). terimlerinin  toplanmasından elde edilir.
a bir tamsayı sabit ifadesi olamak üzere  Fib<a>::value ifadesinin Fibonacci serisinin n. terimi değerinde olmasını sağlayacak uygun sınıf şablonlarını yazınız.

 

Necati Ergin

C ve Sistem Programcıları Derneğinde eğitmen olarak çalışıyor.

Bunlar da ilginizi çekebilir

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Kod Eklemek İçin Okuyun