enable_if

type_traits başlık dosyası içinde yer alan enable_if sınıf yapısı C++11 ile gelen en önemli desteklerden biri:

Önce bu şablonları bir anlamaya çalışalım. Birincil şablonda (primary template) ismi enable_if olan bir yapı tanımlanıyor. Şablonun birinci parametresi bool türden bir sabite bağlanmış. Böyle şablon parametrelerine ingilizcede “template non-type parameter deniyor. İkinci tür şablon parametresi ise varsayılan tür argumanı olarak void türünü almış. Yapımız tamamen boş (empty struct), içinde ne bir veri öğesi ne de bir bildirim var.
Alttaki şablon ise bir “kısmi özelleştirme” (template partial specialization). Birinci şablon parametresi için true sabiti kullanıldığında derleyici kod üretimi için bu şablonu kullanacak. Kısmi özelleştirme içinde yalnızca bir typedef bildirimi var:

Bu typedef bildiriminin birincil şablonda yer almaması yalnızca özelleştirilmiş şablonda bulunması dikkatinizi çekmeli. Bu typedef bildirimiyle ikinci şablon tür parametresine type ismi verilmiş oluyor. Örneğin

türünü kullandığımızda int türünü kullanmış oluyoruz. Diğer taraftan

derleme zamanında geçersiz bir tür bilgisi olacak.

İyi de bu bizim ne işimize yarayacak? Bu yapının ne kadar önemli olduğunu anlayabilmek için popüler olarak SFINAE diye bilinen önemli bir kuralı anlamamız gerekiyor: SFINAE şu sözcüklerin baş harflerinden uydurulmuş bir sözcük: Substitution Failure is not an Error. Türkçeye şöyle çevirelim: Yer değiştirmede başarısızlık sentaks hatası değildir. David Vandevoorde bu terimi ilk olarak bir programlama tekniğini betimlemek için uydurmuş. Peki ne anlama geliyor SFINAE?
Bir işlev şablonu söz konusu olduğunda derleyici şablon tür parametresinin ne olduğunu bilmek ya da anlamak zorundadır. Şablon tür parametresinin ne olacağı ya kodla açıkça belirtilir ya da derleyici tarafından bir çıkarım yapılır. Peki ya bundan sonra ne olur? Derleyici şablonda şablon tür parametresinin bulunduğu yerlerde gerçek tür bilgisini kullanır. Örneğin T türünün geçtiği her yerde int türünü kullanarak bir yerine koyma (substituition) işlemi yapar. Bu yer değiştirme işleminde bir başarısızlık olursa (substitution failure) derleyici bu durumu hemen bir sentaks hatası olarak değerlendirmez. Önce diğer işlev yüklemelerine de (overloads) bakmak zorundadır. Eğer bir başka yükleme işlev çağrısı için geçerli olursa derleyici bu yüklemeyi seçer ve sentaks hatası oluşmaz. Biraz karışık gibi değil mi? Gelin önce SFINAE için bir örnek verelim .Daha sonra enable_if şablonuna geri döneceğiz. Aşağıdaki gibi bir işlev şablonumuz olsun:

Bu işlev şablonuna int türden bir arguman gönderdiğimizde sentaks hatası oluşur:

İlşleve gönderdiğimiz değerden derleyici T türünün int türü olması gerektiğini anladı ve kod üretmek için T türü yerine int türünü kullandı. İşlevin geri dönüş değeri türünün bu durumda

türü olması gerekir ki bu geçerli değil. Şimdi kodumuzu biraz değiştiriyoruz:

Bu kodu derlediğiniz zaman geçerli olacağını göreceksiniz. İlk şablonumuz halen duruyor ama şimdi yeni bir yüklememiz var. Derleyici ikinci yüklemeden yani kodda daha altta bulunan şablondan bir kod üretecek. Üstteki şablondan yapılan açılım girişimi bu kez sentaks hatası ile sonuçlanmadı. İşte SFINAE bu. Artık enable_if şablonuna geri dönebiliriz.
Öyle bir şablon oluşturmak isteyelim ki bu şablondan yalnızca tamsayı türleri için açılım yapılabilsin:

C++’dan daha karmaşık bir programlama dili olmadığına yemin edebilirim.  Şimdi yukarıdaki kodu anlamaya çalışalım:
main işlevi içinde ismi func olan işlev şablonuna int türden 10 değeri gönderilmiş. Bu durumda ikinci şablon tür parametresi için varsayılana arguman değerinin kullanıldığını görüyorsunuz. Bu parametrenin çağrı yapanlar açısından bir önemi yok. Varlık nedeni SFINAE yapısına zemin hazırlamak.

Derleyici işleve yapılan çağrıya gönderilen değerden hareketle T türünün int türü olduğu çıkarımını yapacak. İşlevin birinci parametresinde bir sorun yok. Şimdi ikinci parametrenin geçerli olup olmadığına birlikte bakalım:
Eğer T türü int türü kabul edilirse

değeri bir derleme zamanı sabiti olarak true değerini alacak. Buradan true sabitinin elde edilebilmesi için T türünün tamsayı türlerinden biri olması gerekiyor. Bu durumda ikinci parametremiz

türünden olacak. Birinci şablon parametresinin true olması nedeniyle dilin kurallarına göre derleyici kısmi özelleştirilmiş şablonu kullanacak. Şablon içinde yapılan typedef bildirimi nedeniyle bu tür int türü olacak. Yani ortada bir sentaks hatası yok.
Eğer T türü tamsayı türlerinden biri değil ise ifadenin değeri false olacak ve derleyici bu durumda işlevin ikinci parametresi

türünü kullanacak ve böyle bir tür geçerli olmadığı için şablonda tür yer değiştirmesi başarısızla sonuçlanacak.
Şimdi aklınıza şu soru gelmeli: İşlevimize kullanılmayacak ikinci bir parametre koymak yerine neden işlevin birinci parametresini

türü yapmıyoruz? Ne yazık ki C++ dilini kuralları bu durumda şablon tür parametresinin çıkarımının yapılabilmesini mümkün kılmıyor:

Standart kütüphane onlarca yerde enable_if şablonunu kullanıyor. Gelin bir örneği birlikte inceleyelim:

Bizi şimdilik ilgilendirmeyen detaylardan arındırırsak vector sınıfının 2 parametreli kurucu işlevlerinin şöyle olduğunu düşünebiliriz:

Peki yukarıdaki kodda nasıl oluyor da ivec1 nesnesi için adımlayıcı (iterator) parametreli kurucu işlev çağrılmıyor? Şablon tür parametrelerine verilen isinmlere bakmayın. Aslında bu işlev her türden argumanlar için aday. Çünkü işlevin genel yapısı şu şekilde:

ivec1 nesnesi için

kurucu işlevi çağrılırken

ivec2 ve ive3 nesneleri için

işlevi çağrılıyor. Bir SFINAE kokusu alıyor musunuz? Bu kurucu işlevin bildirimi aslında şöyle

enable_if şablonu ile oluşturulan koşul nedeniyle sadece adımlayıcı sınıfları için bir geçerlilik söz konusu oluyor. Adımlayıcı olmayan bir tür için bu şablon açılımı başarsız olacağından SFINAE ile diğer kurucu işlev yüklemesine bakılıyor.

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