Haftalık C++ 37 – Değişken Şablonlar (“Variadic Templates”)

Evet arkadaşlar uzun bir süredir radarımda olan fakat bir türlü yazıya dökemediğim bir konu olan değişken şablonlar (“variadic templates”)  konusuna bakıyor olacağız. Kabiliyet her ne kadar C++ 11 ile sunulmuş olsa da sonraki C++ standartlarında da, bir takım güncellemelere  ve ilavelere tabi olmuş. Bu kabiliyeti aslında “template metaprogramming” ile uğraşan arkadaşlar muhtemelen oldukça sık kullanıyorlardır, açıkçası ben de her ne kadar ne işe yaradığını bilsem de, çok fazla bu kabiliyeti kullanmadım. O sebeple burada çok detaylı bir inceleme bekliyorsanız, sizi baştan uyarayım. Fakat bunu, daha ileri seviye kullanımları için ilk basamak olarak görebilirsiniz 😉

Şablonlara (“templates”) Kısa Bir Bakış

Asıl konuya dalmadan önce, hızlıca “template” lar nasıl çalışıyora bakmakta fayda var diye düşünüyorum. Bu bağlamda da, hemen bir örnek ile olaya girelim. Örneğin, elimizde tam sayıları toplamak için kullandığımı oldukça sofistike bir fonksiyon olduğunu düşünelim:

Şimdi bunun benzerine float sayılar için de ihtiyaç duyduğumuzu düşünelim ne yapacağız? Ya benzer bir fonksiyonu float için de tanımlayacağız ya da “template”  ları kullanacağız. Nasıl mı? Hemen bakalım:

Evet gördüğünüz gibi tek bir tanımlama ile birden fazla tip için oldukça sofistike bir toplama kabiliyeti elde etmiş olduk. Peki arka planda ne oluyor? Bunu hiç merak ettiniz mi? Hemen bakalım. Bunun için de, uzun bir süredir kullandığım https://gcc.godbolt.org/ sitesini kullanacağız. Bu site aracılığı ile yazmış olduğunuz koda ilişkin üretilen makine kodunu (“assembly”) farklı derleyiciler için görebiliyorsunuz, daha da güzeli, sunulan renklendirme ile satırlar arası ilişki kurmanıza da olanak sağlıyor. Hemen ilk toplama işlemi için x86/64 Clang 11 çıktısına bakalım (ilgili olmayan kısımları çıkardım fakat merak eden okuyucularım, ilgili sitede tam haline bakabilir):

Şimdi aynı kodun “template” lar kullanılarak yazılan haline bakalım

Burada main içerisinde örnek bir kullanım ekledim, çünkü aksi halde template’a karşılık gelen kod üretilmeyecekti. Peki, iki kod arasındaki farkı görebiliyor musunuz? Göremiyor olmanız lazım çünkü ikisi de aslında aynı kodu üretiyor. Gördüğünüz üzere aslında arka planda, derleyici sizin için yine benzer bir kod üretti. Şimdi kayan sayıları kullanan halini de ekleyelim main içerisine bakalım şimdi ne üretecek:

Umarım yeni eklenen satırlarından neden kaynaklandığını anlamışsınızdır. Kayan sayı için de derleyici, arka planda yeni bir fonksiyon bizler için tanımlamış oldu. Burada, aklınıza derleyici ne zamana kadar bunları üretecek sorusu gelebilir. Kodunuz içerisindeki kullanım miktarı kadar. Eee, bu kodu büyütmez mi? Büyütür. Peki bize sıkıntı yaratır mı? Yaratabilir. Keza, emniyet kritik yazılımlar için takip edilen kodlama standartlarında, bütün olası kullanımların başta tanımlanması zorunlu kılınır. Bizim durumumuzda, kodunuzun bir yerinde aşağıdaki satırları tanımlanmış olmanız gerekiyor, bu da derleyicinin ilgili kodları üretmesini tetikleyecektir (hemen deneyebilirsiniz 😉 :

Bu noktada, aslında “template” mekanizması ile uygun fonksiyonların derleme zamanında nasıl tanımlandığını ve kullanımının nasıl olduğunu sanırım artık görmüş olduk. Daha detaylı bilgiler için herhangi bir C++ kaynağına başvurabilirsiniz.

Şimdi gelelim asıl konumuza.

Değişken Şablonlar (“Variadic Templates”)

Uzun lafın kısası, değişken şablonlar bize, sıfır ya da herhangi bir sayıda şablon argümanı alan sınıf ya da fonksiyon şablonları tanımlamamıza olanak sağlarlar. Peki daha önce bu yapılamıyor muydu? Güzel soru, evet yapılabiliyordu, fakat ilgili durumda en fazla adet kadar argüman yine de tanımlanması gerekiyordu. Örneğin, bir tuple şablon sınıfını bu şekilde tanımlayacak olsak nasıl yapardık? İşte söyle:

Bu tanımlama ile N adete kadar argüman alan Tuple sınıfı tanımlayabilirsiniz. Ör:

Ya da ilgili adete göre aşağıdaki gibi kullanımlar da mevcut:

Yukarıdaki örneklerden de görebileceğiniz üzere, bu tarz bir kabiliyet için hem bir çok kod yazmak gerekiyor, ilaveten burada yapılacak hatalara ilişkin hata mesajları da pek insancıl olmayabilir 🙂 Bir de elbette bu argüman sayısına ilişkin bir limit de olacak.

İşte değişken şablonlar bize, yukarıdaki Tuple örneğini çok daha kolay ve anlaşılabilir şekilde aşağıdaki gibi tanımlamamıza izin veriyor:

Bu tanımlama aslında yukarıda yaptığımız tanımlama ile aynı şeyi elde edebiliyoruz.

Şimdi gelelim yukarıdaki tanımlamalara ilişkin hususlara.

Parametre Paketi (“Parameter Pack”)

Öncelikle, Args ibaresinin hemen solundaki “…” ya bakalım (“ellipsis”). Buna şablon tip parametre paketi deniliyor (“template type parameter pack“), Args ise paket ismi olarak ifade edilmektedir. Parametre paketi (“Parameter Pack”), C++ 11 değişken şablonlar ile sunulan bir yapıdır. Normalde tek bir argümana karşılık gelen parametrelerin aksini, bu yapı ile birden fazla argüman tek bir parametre paketi haline getirilebilmektedir.

Örneğin, yukarıda verdiğimiz “typedef tuple<char, short, int, long, long long> integral types;” ile paketlenen parametreler nelerdir? Şunlar: char, short, int, long, long long.

Bu arada, bu parametre paketi içerisine sadece şablon tipleri değil, tip olmayan parametreler ve şablon/şablon parametrelerin kullanımları da mümkün. Hemen örneklere bakalım:

Peki fonksiyon şablonlarında, parametre paketi kullanımı nasıl oluyor. Ona da bakalım:

Yukarıda bahsettiğimiz kullanımlara benzer şekilde, 0 ya da daha fazla argüman ile ilgili fonksiyon çağrılabilmektedir. Ayrıca, tek şablon parametreleri ile yukarıda bahsettiğim parametre paketleri de birlikte kullanılabilmektedir. Burada şöyle bir kısıt var, sadece bir parametre paketi olarak ve bu da en sonda tanımlanmalıdır. Ör:

Burada bahsetmek istediğim bir konu daha var. Özellikle, bu parametre paketlerini isimlendirirken, çoğul bir isim ya da birden fazla parametreyi ifade eden bir kullanım olmasında fayda olabilir.

Paket Genişletme (“Pack Expansion”)

Buraya kadar parametre paketlerinin nasıl tanımlandığını gördük, güzel, peki bunu nasıl kullanacağız? Daha önce de ifade ettiğimiz üzere, bu parametre paketi aslında, virgül ile ayrılan ve tek tek tanımlamaları içeren bir liste haline gelmektedir. Güzel de bunu, örneğin, fonksiyonlara nasıl geçireceğiz? Bunun için de, paket ismi + “…” kullanımına başvuruyoruz. Evet, zihninizde tam netleşmemiş olabilir, o sebeple hemen bir örnek ile bu kullanıma bakalım:

Burada, func(15, 3.14F, “stringExample”, 100) çağrısı ile ilgili parametre tiplerine ilişkin std::tuple’ı açık bir şekilde tanımlamadan, ilgili parametre paketini genişleterek tanımlamasını sağlıyoruz. Yani aslında ne tanımladık? Tam olarak aşağıdakini (dikkat ilk parametre olan int, pakete dahil değil):

İkinci kullanım, yukarıda bahsettiğim aslında sıfır parametre kullanım durumunu tariflemektedir. Peki bu  durumda nasıl bir kod üretiliyor?

Tabi, bunu yapabilmek için bir şeye daha ihtiyacımız var. Nedir peki o? tuple’ın da bu şekilde tanımlanmış olması. Hemen bakalım nasıl tanımlanmış:

İşte bu sayede, bu kullanım mümkün olmaktadır.

Özet olarak bu kullanımları aşağıdaki örnek ile de özetleyebiliriz:

Peki bunlar dışında bu paket genişletme nerelerde kullanılabilir. Aşağıda bunları sıralamaya çalıştım:

  • Fonksiyon argüman listeleri (argüman olarak geçirilmesi),
  • Şablon argüman listeleri (şablon argümanı olarak geçirilmesi),
  • Parantez ilklendirmeleri “()”,
  • Küme ayraçlı ilklendirmeler “{}”
  • Fonksiyon şablon parametre listeleri,
  • Şablon parametre listeleri,
  • Üst veya üye ilklendirme listeleri,
  • Lambda ifadeleri,
  • İstisna (“exception”) tanımlamaları,
  • sizeof… operatörü

Her birine ilişkin detaylı açıklama ve örneklere ise https://en.cppreference.com/w/cpp/language/parameter_pack adresinden ulaşabilirsiniz. Yukarıdakilerden özellikle “sizeof…” a değinmek istiyorum bu konuyu kapatmadan.

sizeof…‘da diğer paket genişletme kullanımlarından birisidir. Basitçe, ilgili paketin kaç elemandan oluştuğunu döner. Burada çalışma zamanında boyut bilgisini dönen sizeof aksine, sizeof…, derleme zamanında oluşturulan bir sabit döner. Hemen bir örneğe bakalım:

Arkadaşlar, sanırım değişken şablonların ne olduğu ve nasıl kullanılabileceğine ilişkin sizlere az da olsa bir fikir verebildiysem ne mutlu. Elbette konu oldukça derin, ayrıca C++ 17 ile de bir takım eklemeler yapılmış (“fold expressions”), buna da inşallah sonraki yazılarımdan birisinde değineceğim. Bu arada bu konuya ilişkin ek bilgi edinmek isteyen yazılımperver dostlarımı kaynaklarda verdiğimi video ve diğer kaynakları incelemeye davet ediyorum.

Bu kabiliyet ile birlikte bir C++ 11 özelliğinin üzerine de çizık atıyoruz. Bir sonraki yazımda görüşmek dileğiyle, bol kodlu günler 🙂

Kaynaklar

Bir cevap yazın

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.