C++ Variadic Templates

Kerim Fırat
5 min readJul 10, 2022

Yazı serilerine devam ederken,özellikle “Modern C++”” konularını daha iyi kavrayabilmek için “Variadic Templates” konusunu işlemeden geçemeyeceğiz.
Örneğin “Modern C++” serisinin ilk partı olan “variant” içeriği bu konuyu kapsamaktadır. Nitekim ilgili konuya ait aşağıdaki yazı parçası ve benzeri yapı bu konu içeriğinde çokça geçecektir:

…..
Yaptığımız tanımlama ve kullanım üzerinden detaya inmeden evvel,variant’ın ana yapısı aşağıda verilmiştir.
template<class… Types>
…..

Variadic

“Variadic” kavramı,aslında temeli C’de olan ve “Variadic functions” kavramı ile yer alan ve değişken sayıda argüman-parametre alabilen fonksiyonlar yapısına dayanır. Bu özellik C++’a her ne kadar C++11 ile gelmiş olsa da,çok önceleri C’de aşağıdaki şekilde kullanılıyordu.
Örnek tanımlama:

Nitekim aşina olduğumuz “printf” fonksiyonu “variadic” yapısında olan en bilinen fonksiyondur.
C’de variadic yapısını ve işleyişini incelemek isteyenler için bu link faydalı olacaktır. Yine aynı şekilde “printf” yapısını ve işleyişini incelemek isteyenler için bu link faydalı olacaktır.

NOT: variadic kavramı ile ilgili kapsamlı(derinleme yapısı,derleyicilerin kod üretimi-davranışları vb.) türkçe kaynak var mı açıkçası bilmiyorum.
Derinlemesine ilgi duyanlar varsa bu konuda da kapsamlı bir yazı hazırlarım. Ancak genel olarak derleyici seviyesinde meraklı bulamadığım için çok fazla derine inmek istemiyorum.

Template and Compiler

Yazı başlığından da anlaşılacağı gibi aslında template kavramını belli bir seviye bildiğinizi varsayarak bu yazıyı hazırladım.
Ancak variadic kavramını daha iyi kavrayabilmemiz için,derleyicinin gözünden en basit template yapısından başlayarak incelemeyi faydalı buluyorum. En temel haliyle template örneği aşağıda verilmiştir.

Programlama yaparken bir template tanımlaması yapılır fakat kullanılmaz ise,derleyiciler ilgili tanımlamaya ait kod üretmeyecektir.
Dolayısıyla ilgili template yok sayılacaktır ve üretilen binary içinde asla yer almayacaktır. Bu bilgi basit template kullanımı ve işleyişini kapsar. Bu hatırlatma ile bu noktayı hızlıca geçmiş olalım ve aşağıda verilen kullanımı inceleyelim.

https://godbolt.org/

Çıktıyı incelediğimizde,template fonksiyonuna atanan veri tür-tipine göre fonksiyon kod üretildiğini görüyoruz.

Main içeriğine aşağıdaki gibi eklemeler yapıp üretilen kodu tekrar inceleyelim.

Output: godbolt linki

Görüldüği gibi atanan veri tür-tipine göre kod üretilmiştir.
Bu süreci manuel olarak ele alırsak,aslında her veri tür-tipine göre fonksiyon yazmamız gerekirken,template kavramı biz geliştiricileri bu zahmetten kurtarıyor.Yani bunun anlamı aslında geliştiriciyi tüm bu tekrar eden fonksiyonları yazmak için efor sarfetmekten kurtarmaktır. Compiler çıktısına bakmadan bu yapıyı anlamak için verilen kodu aşağıdaki gibi değiştirip tekrar çalıştıralım ve çıktıyı inceleyelim.

NOT:”__PRETTY_FUNCTION__”, içinde yer aldığı fonksiyonun ismini tutar.
Detaylı incelemek için link.

Kod linki

Output:

Soru: Ben bu fonksiyona bir int bir de double veri girişi,yani toplamda iki tür veri tipiyle uğraş verecem. Bunun için template kullanmam çok mu alengilli iş olur?
Cevap: Kodunu okuyacak kişi template tanımını gördüğünde,”Ahaa tür-tip bağımsız zamazingo işler dönüyor” olduğunu bilir. Dolayısıyla birden fazla tür-tip var olduğu kabul edilir.
Yani bu durumda da template kullanmak caizdir. Fakat template kullanmanın bazı dezavantajları vardır. Bu dezavantajlara bakarak seçim yapmak daha doğru olacağını düşünüyorum.
///
Soru: Kod okurken nice template fonksiyonlar gördüm ki tek adet veri tip-türü için tasarlanmış.Yani eleman template tasarlamış ancak sadece int veri geçmiş. Buna ne buyrulur?
Cevap: Eğer okuduğun kod parçası gelişmekte olan bir projeye ait ise anlayış gösteriyoruz. Ancak nihayi bir halde ise refector yapmayı uygun görüyoruz.

Variadic templates

Yazının girişinde belirttiğim C “variadic functions” kavramı C++11 ile yeni bir özellik halinde bize sunuluyor. Yeni özellik diyoruz çünkü C’deki hali run-time sürecinde işlenen yapı,C++11 ile run-time yerine compile-time sürecine çekiliyor.

Ancak iki durumu da daha detaylı ele alırsak,aslında C’deki halinin compiler optimizasyonunun genel hale getirilmesi ve syntax değişikliği ile C++11 özelliği haline getirilmesidir. Bunları tartışmak dil tasarımı konularına girdiği için burada daha fazla üzerinde durmaya gerek yoktur.
Variadic template genel tanımlama biçimi aşağıdaki gibidir:

Aşağıda birden fazla parametre ile fonksiyon kullanıma yer verilmiştir:
Kod linki

Yukarıda verilen örnekte set_data fonksiyonuna 5 adet parametre verilmiştir. Ancak çıktıyı incelersek sadece ilk paremetre olan “5” değeri görülür.
Eğer C konusuna tekrar dönersek,variadic fonksiyon içerisinde gelen parametreleri okumak için “va_” ile başlayan birtakım komutların kullanıldığını göreceğiz.
Dolayısıyla gelen parametreleri “va_list” ile bir pointer(array) tanımlanması ve devamında ilgili array üzerinde bir for dönerek içerik elde ediliyor.
Aynı mantıkla yukarıdaki parametreleri elde edeceğiz. Ancak biraz farklı şekilde.

Kod linki

Yukarıda verilen örneği çalıştırdığımızda tüm parametre değerlerinin okunduğunu göreceğiz.
Koda iki ekleme yapıldı. İlk ekleme recursive,ikinci ekleme ise aynı ismiyle içeriği boş bir fonksiyon eklenmiş olması.
Çünkü recursive sürecinin sonuna ulaşıldığında,”set_data” fonksiyonunun parametresiz haline gerek vardır.

Variadic tanımlamanın compiler tarafına baktığımızda üretilen kod aşağıdaki gibidir. Bu çıktıda “recursive” satırı kapalı(yorum satırı) haldedir.Bu nedenle 5 adet parametreli tek bir fonksiyon için kod üretildiği görülmektedir.

Ancak “recursive” satırını aktif eder-kullanırsak,bu durumda her parametre sayısı için fonksiyon kodu üretecektir.
Evet bunu yapacaktır çünkü compiler’lar oldukça aptal seviyedeler ve birilerinin bunları akıllı hale getirmesi gerek.

Aynı şekilde “__PRETTY_FUNCTION__” ekleyerek çıktıyı inceleyelim.

Kod linki

Output:

void set_data(T, Types …) [with T = int; Types = {int, int, int, int}]
5
void set_data(T, Types …) [with T = int; Types = {int, int, int}]
10
void set_data(T, Types …) [with T = int; Types = {int, int}]
20
void set_data(T, Types …) [with T = int; Types = {int}]
30
void set_data(T, Types …) [with T = int; Types = {}]
40
void set_data()

Variadic işleyişinin farklı veri tip-türleriyle kullanımına aşağıda yer verilmiştir.

Compiler kod çıktısı ve program çıktısı için link= https://godbolt.org/z/Y6Tjz68Yh
Burada “set_data” fonksiyonuna parametre geçerken aşağıdaki şekilde de kullanılabilir.
Yani parametre geçilecek veri tip-türlerini belirterek de kullanabiliriz. Kod okumayı kolaylaştırmak veya “clean code” sağlamak için bu kullanım önerilir.
Örnek:

Aşağıdaki örnekte variadic fonksiyonuna geçilen parametreleri çift halinde ele alarak bir karşılaştırma işlemi yapılıyor.
Tüm çiftlerin doğru olması durumunda fonksiyon bool türünde bir değer döndürüyor. Örnek:

Output:
son çift için çağrılan fonksiyon:3,3
true

Bu örnekte ilk çift “1,1”,…,son çift ise “3,3” olarak verilmiştir.
Her çift ayrı şekilde ele alınarak karşılaştırılıyor ve tüm çiftlerin karşılaştırılması eşit olduğu için sonuç “true” döndürüyor.
Bu örnekte toplamda 4 adet çift vardır. Fakat fazladan bir adet parametre geçersek variadic yapısı recursive noktasında hata gösterecektir.
Ekleme yapılan satır:

Hata çıktısı link

Bu sorunu çözmek için aynı isimle bir fonksiyon tanımlaması daha yapmamız gerekir.

Son hali linki

Output:
tek parametre a:4
false

Bildiğimiz gibi template’lar compiler-time’da ele alınır. Dolayısıyla compiler,gerek görmediği fonksiyon ve yapılar için kod üretmez.
Son eklemeden sonra kod içinde bulunan bir fonksiyon için compiler tarafında kod üretimi yapılmayacaktır. İlgili fonksiyonu bulmak-belirlemek okuyucunun ödeviniz olsun.

Sonuç

Modern C++ konularında “variadic templates” yapısı-tasarımına sahip bolca feature mevcuttur.
Örneğin smart pointer’lar bu tasarımlara sahiptir. Variadic oldukça kapsamlı bir konudur ve çok daha karmaşık örneklerle incelemek faydalı olacaktır.
Fırsat buldukça bu konuda birkaç yazı daha yazmayı planlıyorum.
Ayrıca “template,variadic” kullanımlarına ait avantaj ve dezavantajları “High Performance-Compiler-Build” başlıklı yazı serilerinde detaylıca işleyeceğiz.
Açıkcası bağımsız dil tasarımcısı gözüyle baktığımda,”variadic” çok da iyi bir tasarım olarak görmüyorum ve eksikleri çok.
İleriki yazılarda dil tasarımları üzerine konuşurken bu eksikleri dile getirir öneriler üzerinden konuşacağız.
Öte yandan C’deki kullanım ile karşılaştırdığımda,şahsi fikrim yine kötü bir tasarım olduğu yönündedir. Bazı geliştiriciler tarafından C’deki kullanıma göre çok daha güvenli olduğu söylenmiş ve söyleniyor. Ben buna katılmıyorum. Eğer bu yapı compiler-time değil de,C deki gibi run-time sürecinde ele alınsaydı, eminim ki farklı şeyler söyleyeceklerdi. Neyseki “template” kavramı bu durumu bypass ediyor.

KAYNAKLAR:
https://en.cppreference.com/w/c/variadic
https://github.com/lattera/glibc/blob/master/stdio-common/printf.c
https://elixir.bootlin.com/linux/latest/source/arch/x86/boot/printf.c#L294
https://gcc.gnu.org/onlinedocs/gcc-3.3.6/gcc/Function-Names.html#:~:text=The%20identifier%20__PRETTY_FUNCTION__,in%20a%20language%20specific%20fashion.&text=On%20the%20other%20hand%2C%20%60%20%23,the%20identifier%20__FUNCTION__%20.

-end of

--

--

Kerim Fırat

Senior C++,Android Platform(AOSP,AAOS) Developer,Performance Architect,Open Source Contributor | Turkey Java User Group Vice Chairman | Author