---
title: 'Değişken Sayıda Şablon Parametresi'
source: 'https://academia.sh/tr/kurslar/cpp-sablonlar/degisken-sayida-sablon-parametresi'
course: 'Şablonlar ve Başarım'
language: tr
updated: '2026-10-21T05:02:46+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Parametre paketini tanımlar ve sizeof... ile paket açmayı kullanır.'
    - 'Özyineli açma ile katlama ifadesinin derleyiciye yazdırdığı gövde sayısını karşılaştırır.'
    - 'Aynı tip listesinin gövdeyi yeniden kullandığını, yeni bir tipin ise yeni gövdeler doğurduğunu gösterir.'
---

# Değişken Sayıda Şablon Parametresi

Parametre paketi, sizeof..., paket açma, özyineli açma ile katlama ifadesi karşılaştırması, std::forward ile iletme ve açma yönteminin derleyiciye kaç gövde yazdırdığı.

Önceki ders özelleştirmeyi ölçtü: `Paket<bool>` bedeli yuvadan çıkarıp koşumdaki bir adıma
koydu. Bu ders şablonun parametre sayısını da serbest bırakıyor. Bir işlev, kaç argüman
verilirse versin, hepsini kabul edebilir mi? **Değişken sayılı şablon** bunu sağlar, ama
açma yöntemi derleyiciye kaç gövde yazdırdığını değiştirir. Dersin sorusu o sayıdır: bir
paketi açmak kaç gövde doğurur?

## Parametre Paketi ve sizeof...

Üç noktalı `typename... T` bildirimi bir **parametre paketi** tanımlar: sıfır ya da daha
çok tipi tek bir ad altında toplar. İşlev parametresi `T... x` ise buna karşılık gelen
**işlev parametre paketidir**. Paketteki eleman sayısı çalışma zamanında değil derlemede
bilinir ve `sizeof...(T)` ile okunur. Paketi kullanmak için `x...` yazılır; buna **paket
açma** denir: derleyici paketin her elemanını virgülle ayrılmış bir listeye çevirir.

Paketi başka bir işleve iletmek için `std::forward` kullanılır. Bir argümanın sol değer ya
da sağ değer olduğu bilgisi, `A&&` biçimindeki parametrede korunur; `std::forward<A>` o
bilgiyi bozmadan iletir. Aşağıdaki program üç şeyi birlikte gösteriyor.

```cpp
// paket-islemleri.cpp — sizeof..., iletme ve tekli katlama
#include <cstdio>
#include <utility>

template <typename... T>
constexpr int say(T...) { return static_cast<int>(sizeof...(T)); }

template <typename... T>
bool hepsi_pozitif(T... x) { return (... && (x > 0)); }

static long topla3(long a, long b, long c) { return a + b + c; }

template <typename F, typename... A>
long ilet(F f, A&&... a) { return f(std::forward<A>(a)...); }

int main() {
    std::printf("say %d %d %d\n", say(), say(1), say(1, 'a', 2.0));
    std::printf("hepsi %d %d %d\n", hepsi_pozitif(1, 2, 3) ? 1 : 0,
                hepsi_pozitif(1, -2) ? 1 : 0, hepsi_pozitif() ? 1 : 0);
    std::printf("ilet %ld\n", ilet(topla3, 1, 2L, 3));
    return 0;
}
```

```text
say 0 1 3
hepsi 1 0 1
ilet 6
```

**MP13.** `say` boş paketle sıfır, üç argümanla üç verir; sayı derlemede sabittir. `hepsi_pozitif`
bir **katlama ifadesi** kullanır: `(... && (x > 0))`, paketi `&&` işleciyle tek bir
ifadeye katlar. Paket boşken `&&` katlaması `true` verir. `ilet` argümanları hiçbir
kopya eklemeden `topla3`e gönderir; `std::forward` yalnız burada, iletme için gerekli.

## Özyineli Açma ve Taban Durumu

Paketi elle açmanın klasik yolu üç adımdır: ilk argümanı al, geri
kalanıyla aynı işlevi çağır, paket boşalınca duran bir **taban durumu** yaz. Her çağrı bir
eleman daha eksik bir paketle yapılır, yani her adım **ayrı bir şablon gövdesidir**.

Toplamı iki yolla kuralım. `topla_oz` özyineli açar; `topla_kat` aynı toplamı tek katlama
ifadesiyle verir. Her gövde kendi yerel `static`'ini ilk çağrıda bir kez artırır, yani
sayaç üretilen gövdeleri sayar.

```cpp
// toplam.hpp — ozyineli ve katlamali toplam, govdeleri sayar
#ifndef TOPLAM_HPP
#define TOPLAM_HPP

inline int oz_govde = 0;
inline int kat_govde = 0;

inline long topla_oz() {
    static const bool kayit = (++oz_govde, true);
    (void)kayit;
    return 0;
}

template <typename Ilk, typename... Kalan>
long topla_oz(Ilk ilk, Kalan... kalan) {
    static const bool kayit = (++oz_govde, true);
    (void)kayit;
    return ilk + topla_oz(kalan...);
}

template <typename... T>
long topla_kat(T... x) {
    static const bool kayit = (++kat_govde, true);
    (void)kayit;
    return (0L + ... + x);
}

#endif
```

```cpp
// paket-acma.cpp — ayni toplam, iki acma yontemi
#include <cstdio>

#include "toplam.hpp"

int main() {
    long a = topla_oz(1, 2, 3);
    long b = topla_kat(1, 2, 3);
    std::printf("toplam %ld %ld\n", a, b);
    std::printf("ozyineli %d katlama %d\n", oz_govde, kat_govde);
    return 0;
}
```

```text
toplam 6 6
ozyineli 4 katlama 1
```

**MP14.** İki yöntem de altı verir, ama gövde sayıları ayrıdır. `topla_oz(1, 2, 3)` üç
şablon gövdesi (`<int,int,int>`, `<int,int>`, `<int>`) ve bir taban işlevi kullanır: dört.
`topla_kat(1, 2, 3)` aynı toplamı tek gövdeyle verir. Çekirdekteki `ozyineli paket acma`
satırının `ozyineli=4 katlama=1` sayıları bu ölçümdür. Bedel **derleme** sınıfına düşer:
koşumda karar adımı kalmaz, derleyici her açma biçimi için ayrı sayıda kod yazar.

## Katlama İfadesinin Biçimleri

Katlama ifadesi dört biçimde yazılır. **Tekli** katlama `(... + x)` ya da `(x + ...)`
yalnız paketi taşır; **ikili** katlama `(0L + ... + x)` bir başlangıç değeri ekler. Sol
katlama `((x1 + x2) + x3)` gibi, sağ katlama `(x1 + (x2 + x3))` gibi gruplar. Başlangıç
değeri taşımayan tekli katlama, boş paket için yalnız `&&`, `||` ve `,` işleçlerinde
geçerlidir: `(... && ...)` boş pakette `true`, `(... || ...)` `false`, virgül `void()`
verir. `+` ile tekli katlama boş pakette derlenmez; `topla_kat()` çağrısı için `0L`
başlangıç değeri bu yüzden gerekli. Katlama ifadesi her somutlaştırmada tek gövdeyle
çalışır, çünkü paket açma bir özyineleme değil, derleyicinin ürettiği tek bir ifadedir.

## Açma Kalıbı: Üç Noktanın Sağ Tarafı

Üç noktanın anlamı sağında durduğu ifadeye bağlıdır. `x...` yalnız `x`'i açar; ama
`(x * 2)...` kalıbın tamamını her eleman için bir kez yazar. Kalıp içinde birden çok paket
varsa hepsi aynı uzunlukta olmalıdır ve eleman eleman birlikte açılır. Açma yeri de
önemlidir: işlev çağrısı argüman listesinde, küme başlatıcıda, şablon argüman listesinde ya
da bir katlama ifadesinde açılabilir. Aşağıdaki program bunlardan ikisini, küme başlatıcıyı ve
katlama ifadesini gösteriyor.

```cpp
// kalip.cpp — kalip acma ve ayni tip sorgusu
#include <cstdio>
#include <type_traits>

template <typename... T>
int ikilisi(T... x) {
    int d[] = {static_cast<int>(x * 2)..., 0};
    int t = 0;
    for (int v : d) {
        t += v;
    }
    return t;
}

template <typename Ilk, typename... T>
constexpr bool hepsi_ayni_v = (std::is_same_v<Ilk, T> && ...);

template <typename... T>
void yaz(T... x) {
    int sira = 0;
    (std::printf("%s%d", sira++ ? "," : "", static_cast<int>(x)), ...);
    std::printf("\n");
}

int main() {
    std::printf("ikilisi %d\n", ikilisi(1, 2, 3));
    std::printf("ayni %d %d\n", hepsi_ayni_v<int, int, int>,
                hepsi_ayni_v<int, int, long>);
    yaz(7, 8L, 'a');
    return 0;
}
```

```text
ikilisi 12
ayni 1 0
7,8,97
```

**MP15.** `ikilisi(1, 2, 3)` kalıbı üç kez yazar: `{2, 4, 6, 0}` kümesi toplamda on iki verir.
Sondaki sıfır, boş pakette `{}` yerine geçerli bir küme sağlar. `hepsi_ayni_v` bir tip
sorgusudur ve katlama ifadesi `&&` ile tüm eşleşmeleri toplar. `yaz` virgül işleciyle
katlanır; virgül katlamasında elemanlar soldan sağa değerlendirilir, bu standardın
garantisidir ve çıktıdaki sıra bunu gösterir (97, `a` karakterinin kodudur).

## İleten Yapıcı: Paket ve std::forward

Paketin en sık görülen kullanımı, bir nesneyi kurarken argümanları olduğu gibi yapıcıya
iletmektir. `A&&...` biçimindeki parametreler **iletme referansı** paketi oluşturur ve
`std::forward<A>(a)...` her elemanın sol değer ya da sağ değer olduğunu korur. Bunu
görmek için kopyalanıp taşınmasını saydığımız bir tip yazalım.

```cpp
// ileten.cpp — ileten yapici, kopya ve tasima sayisi
#include <cstdio>
#include <utility>

struct Iz {
    static inline int kopya = 0;
    static inline int tasima = 0;
    int deger;
    explicit Iz(int d) : deger(d) {}
    Iz(const Iz& o) : deger(o.deger) { ++kopya; }
    Iz(Iz&& o) noexcept : deger(o.deger) { ++tasima; }
};

struct Sarmal {
    Iz a;
    Iz b;
    template <typename A, typename B>
    Sarmal(A&& x, B&& y) : a(std::forward<A>(x)), b(std::forward<B>(y)) {}
};

template <typename T, typename... A>
T olustur(A&&... a) {
    return T(std::forward<A>(a)...);
}

int main() {
    Iz sol(1);
    Sarmal s1 = olustur<Sarmal>(sol, Iz(2));
    std::printf("kopya=%d tasima=%d\n", Iz::kopya, Iz::tasima);
    Sarmal s2 = olustur<Sarmal>(Iz(3), Iz(4));
    std::printf("kopya=%d tasima=%d\n", Iz::kopya, Iz::tasima);
    std::printf("degerler %d %d %d %d\n", s1.a.deger, s1.b.deger, s2.a.deger, s2.b.deger);
    return 0;
}
```

```text
kopya=1 tasima=1
kopya=1 tasima=3
degerler 1 2 3 4
```

**MP16.** İlk kurulumda `sol` bir sol değerdir, `std::forward<Iz&>` onu kopyalar; geçici
`Iz(2)` bir sağ değerdir ve taşınır. İkinci kurulumda iki geçici de taşınır. `std::forward`
olmasa her argüman adlı bir sol değer olarak yapıcıya gider ve her seferinde kopyalanırdı.
Sayaçlar birikimlidir, ikinci satır ikinci kurulumun iki taşımasını da içerir; bunlar ürün sayısıdır, süre iddiası değildir. Bu iletme zinciri de her argüman tipi
birleşimi için ayrı bir `olustur` gövdesi yazdırır, yani bedel yine **derleme** sınıfındadır.

## Paket ve Sınıf Şablonu

Paket yalnız işlevlerde değil sınıf şablonlarında da bulunur. `std::tuple<int, double, char>`
bunun örneğidir. Kendi tip listemizi taşıyan boş bir sınıf yazıp eleman sayısını
`sizeof...` ile sorabiliriz; sınıfın kendisi bir değer taşımaz, yalnız tipleri adlandırır.

```cpp
// liste.cpp — tip listesi tasiyan sinif sablonu
#include <cstdio>
#include <type_traits>

template <typename... T>
struct TipListesi {
    static constexpr int boy = static_cast<int>(sizeof...(T));
};

template <typename L>
struct Boy;

template <typename... T>
struct Boy<TipListesi<T...>> {
    static constexpr int deger = sizeof...(T);
};

static_assert(TipListesi<int, char>::boy == 2);
static_assert(Boy<TipListesi<>>::deger == 0);

int main() {
    using L = TipListesi<int, double, char>;
    std::printf("boy %d %d\n", L::boy, Boy<L>::deger);
    std::printf("bos %d\n", TipListesi<>::boy);
    return 0;
}
```

```text
boy 3 3
bos 0
```

**MP17.** `TipListesi<>` boş bir pakettir ve geçerlidir; `sizeof...` sıfır verir. `Boy`
kısmi özelleştirmesi `TipListesi<T...>` kalıbıyla paketi geri toplar: önceki dersin kısmi
özelleştirmesi, bu kez paketle. Hiçbir koşum maliyeti yoktur, çünkü iki `static_assert`
ve üyeler derlemede sabitlenir.

## Paketi Saklamak: tuple, apply ve index_sequence

Bir paket doğrudan saklanamaz; bir değişkene atanamaz ve bir kapsayıcıya konamaz. Saklamak
için standart kütüphane `std::tuple` verir: farklı tiplerden elemanları tek bir değerde
toplar. `std::apply` bir demetin elemanlarını bir işlevin argümanları olarak açar, yani
demet ile paket arasında köprü kurar. Elemanlara sıra numarasıyla erişmek gerekirse
`std::index_sequence<I...>` derleme zamanında `0, 1, 2, ...` dizisini bir paket olarak
üretir. Bu üç araç özyineleme yazmadan paketin elemanlarını konumlarıyla birlikte işler.

```cpp
// demet.cpp — tuple, apply ve index_sequence
#include <cstdio>
#include <tuple>
#include <type_traits>
#include <utility>

template <typename T, std::size_t... I>
long agirlikli(const T& t, std::index_sequence<I...>) {
    return (((static_cast<long>(I) + 1) * std::get<I>(t)) + ...);
}

template <typename... A>
long agirlikli_toplam(A... a) {
    auto t = std::make_tuple(a...);
    return agirlikli(t, std::index_sequence_for<A...>{});
}

int main() {
    auto t = std::make_tuple(2, 3, 4);
    long duz = std::apply([](auto... e) { return (0L + ... + e); }, t);
    std::printf("apply %ld\n", duz);
    std::printf("agirlikli %ld\n", agirlikli_toplam(2, 3, 4));
    using ikinci = std::tuple_element_t<1, std::tuple<int, double, char>>;
    std::printf("tip %d\n", std::is_same_v<ikinci, double>);
    return 0;
}
```

```text
apply 9
agirlikli 20
tip 1
```

**MP18.** `std::apply` demeti açar ve katlama ifadesi `2 + 3 + 4` toplamını verir. `agirlikli`
her elemanı konum numarasıyla çarpar: `1*2 + 2*3 + 3*4`. `index_sequence_for<A...>` paketle
aynı uzunlukta bir dizi üretir ve `I...` bu diziyle `A...` ile birlikte açılır. Üçüncü
satırda `tuple_element_t<1, ...>` paketten ikinci tipi seçer; `double` olması beklenir
ve sorgu bunu doğrular. Hepsi derlemede çözülür, her farklı eleman sayısı için ayrı bir
gövde yazdırır, yani yine **derleme** sınıfındadır.

Paketin sınırları da var. Boş bir `std::tuple<>` geçerlidir, ama boş pakette `[0]` gibi bir
konum yoktur. Paket açma ancak bir kalıbın içinde yapılabilir; çıplak `x...` tek başına
bir ifade değildir. Bir sınıf şablonunda paket parametresi son
parametre olmalıdır. İşlev şablonunda paketin ardından parametre gelebilir, ama o parametre
çıkarılabilmeli ya da varsayılan bir argüman taşımalıdır; `template <typename... A,
typename B>` gibi bir sıra, `B` çıkarılamıyorsa kullanılamaz. Özyineli açmada paketin bir
ilk elemanla (`Ilk, Kalan...`) birlikte yazılması ise başka bir nedene dayanır: ilk elemanı
ayırıp geri kalanla özyinelemek.

## Sınırlayıcı Ölçüm

Gövde sayısı çağrı sayısına değil, **tip listesine** bağlıdır. Aynı tip listesiyle ikinci
bir çağrı mevcut gövdeleri kullanır; farklı tipli bir çağrı yenilerini üretir.

```cpp
// paket-yeniden.cpp — ayni tip listesi govdeyi yeniden kullanir
#include <cstdio>

#include "toplam.hpp"

int main() {
    topla_oz(1, 2, 3);
    topla_kat(1, 2, 3);
    std::printf("ilk        ozyineli %d katlama %d\n", oz_govde, kat_govde);

    topla_oz(4, 5, 6);
    topla_kat(4, 5, 6);
    std::printf("ikinci     ozyineli %d katlama %d\n", oz_govde, kat_govde);

    topla_oz(1, 2, 3L);
    topla_kat(1, 2, 3L);
    std::printf("yeni tipli ozyineli %d katlama %d\n", oz_govde, kat_govde);
    return 0;
}
```

```text
ilk        ozyineli 4 katlama 1
ikinci     ozyineli 4 katlama 1
yeni tipli ozyineli 7 katlama 2
```

**MP19.** `(4, 5, 6)` çağrıları da `int, int, int` listesidir: gövde sayıları değişmez,
dörtten dörde. `(1, 2, 3L)` ise yeni bir tip listesidir: `<int,int,long>`, `<int,long>` ve
`<long>` üç yeni özyineli gövde doğurur, dörtten yediye çıkar; taban işlevi aynı kalır.
Katlama tarafında da yeni liste bir yeni gövde ekler. Bu yüzden sayı ölçünün kendisi
değildir; bedelin hangi sınıfa yazıldığını gösteren kanıttır: özyineli açma tip çeşitliliği arttıkça yeni gövde
yazdırır, ve bu bedel **derleme** sınıfında kalır.

## Özet

- Bu dersin ölçtüğü düzenek bedeli **derleme** sınıfına taşır: bir paketi açmak, açma
  yöntemine göre farklı sayıda gövde üretir.
- `typename... T` bir parametre paketidir; `sizeof...(T)` eleman sayısını derlemede verir,
  `x...` paketi açar.
- Özyineli açma her adımda ayrı bir şablon gövdesi ve bir taban durumu ister
  (`ozyineli=4`); katlama ifadesi aynı iş için tek gövde yeter (`katlama=1`).
- Tekli katlama boş paketi yalnız `&&`, `||` ve `,` ile kabul eder; ikili katlama
  başlangıç değeri taşır.
- Aynı tip listesi gövdeyi yeniden kullanır (4 → 4); yeni bir tip listesi yeni gövdeler
  doğurur (4 → 7).

## Sonraki Adım

Bu derste paket açma, tip listesine göre gövde yazdırdı ve bedel derlemede kaldı.
Sıradaki ders, tipin kendisine soru soran araçları, tip özelliklerini ele alıyor: bir
şablonun içinde `if constexpr` ile yapılan seçim koşumda kaç sorgu bırakıyor?
