---
title: 'Aşırı Yükleme Çözümlemesi ve SFINAE'
source: 'https://academia.sh/tr/kurslar/cpp-sablonlar/asiri-yukleme-cozumlemesi-ve-sfinae'
course: 'Şablonlar ve Başarım'
language: tr
updated: '2026-10-21T05:02:46+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Aşırı yükleme çözümlemesinde aday, uygun aday ve en iyi aday ayrımını yapar.'
    - 'SFINAE ilkesini ve enable_if_t ile aday elemeyi uygular.'
    - 'void_t ile bir ifadenin geçerli olup olmadığını algılayan tip özelliği yazar.'
    - "SFINAE'nin yalnız bildirimin hemen bağlamını kapsadığını, gövdedeki hatanın derlemeyi durdurduğunu açıklar."
---

# Aşırı Yükleme Çözümlemesi ve SFINAE

Aday ve uygun aday kavramı, şablon ile şablon olmayan işlevin yarışı, enable_if_t ile aday eleme, void_t ile algılama ve C++20 kavramlarıyla kısıtlama.

Önceki ders tipe bakıp dal seçmeyi gösterdi: karar bir `if constexpr` içinde veriliyordu.
Bir adım önce bir soru daha var. Aynı adla birkaç işlev varsa derleyici hangisini çağırır
ve uygun olmayan bir şablon aday listesinde ne olur? Bu ders çağrının nasıl seçildiğini ve
şablonun yanlış tiple karşılaştığında hata vermeden nasıl elendiğini ölçüyor. Seçim
derlemede biter ve bedel yine **derleme** sınıfındadır.

## Aday, Uygun Aday, En İyi Aday

Bir işlev çağrısında derleyici üç aşamadan geçer. Önce çağrılan adla görünen tüm işlevleri
ve işlev şablonlarını **aday** olarak toplar. Sonra argüman sayısı ve dönüşümü uygun
olanları **uygun aday** listesine alır. Son olarak uygun adaylar arasında argümanlara en
iyi uyan **en iyi aday** seçilir. Eşit iyilikte iki aday kalırsa ve hiçbir eşitlik kuralı
birini öne çıkarmazsa çağrı belirsizdir ve derleme durur.

Şablon ile şablon olmayan işlev eşit iyilikte yarışırsa şablon olmayan kazanır. Ama
şablon, argüman tipine tam uyum sağlarken şablon olmayan işlev bir dönüşüm gerektiriyorsa
şablon kazanır, çünkü tam uyum dönüşümden iyidir.

```cpp
// yukleme.cpp — sablon ve sablon olmayan aday
#include <cstdio>
#include <string>

const char* sec(int) { return "int"; }
const char* sec(double) { return "double"; }

template <typename T>
const char* sec(T) { return "sablon"; }

int main() {
    std::printf("sec(1)      %s\n", sec(1));
    std::printf("sec(1.5)    %s\n", sec(1.5));
    std::printf("sec('a')    %s\n", sec('a'));
    std::printf("sec(2.5f)   %s\n", sec(2.5f));
    std::printf("sec(string) %s\n", sec(std::string("x")));
    return 0;
}
```

```text
sec(1)      int
sec(1.5)    double
sec('a')    sablon
sec(2.5f)   sablon
sec(string) sablon
```

`sec(1)` ve `sec(1.5)` için şablon olmayan işlev tam uyar ve eşitlikte kazanır. `'a'` bir
`char`'dır; `int` adayı bir tamsayı terfisi gerektirir, şablon ise `T = char` ile tam uyar,
bu yüzden şablon kazanır. `float` için de aynıdır. `string` için yalnız şablon uygun
adaydır.

## SFINAE: Yerine Koyma Başarısızlığı Hata Değildir

Şablon argümanı çıkarıldıktan sonra derleyici çıkarılan tipi şablonun bildirimine koyar.
Bu koyma geçersiz bir tip ya da ifade üretirse, o şablon aday listesinden sessizce düşer
ve derleme hata vermez. İlke **SFINAE** adıyla bilinir: yerine koyma başarısızlığı hata
değildir (substitution failure is not an error). Hata ancak hiçbir aday kalmazsa
verilir.

SFINAE'yi kasıtlı kullanmanın klasik yolu `std::enable_if_t<koşul, tip>`'tir: koşul
doğruysa `tip`'e eşittir, yanlışsa yerine koyma başarısız olur ve şablon elenir. Çoğunlukla
tür olmayan bir şablon parametresinin tipine yerleştirilir ve o parametreye `= 0` gibi bir
varsayılan değer verilir.

```cpp
// enable-if.cpp — enable_if_t ile aday eleme
#include <cstdio>
#include <type_traits>

template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
const char* tur(T) { return "tamsayi"; }

template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
const char* tur(T) { return "kayan"; }

int main() {
    std::printf("tur(7)   %s\n", tur(7));
    std::printf("tur(2.5) %s\n", tur(2.5));
    return 0;
}
```

```text
tur(7)   tamsayi
tur(2.5) kayan
```

**MP26.** `tur(7)` için ikinci şablonun bildirimi `enable_if_t<false, int>` olur, yerine
koyma başarısız olur ve ikinci aday elenir; geriye `tamsayi` kalır. `tur(2.5)` için tersi
olur. İki şablonun imzası aynı görünür, ama şablon parametrelerinin tipleri farklı
koşullara bağlı olduğundan imzalar farklıdır ve çakışma olmaz. Seçim derlemededir ve koşumda sorgu
bırakmaz.

## Dönüşüm Sıralaması

Uygun adaylar arasında "en iyi"nin ne demek olduğu argümanın her adaya hangi dönüşümle
bağlandığına bakar. Dönüşümler iyiden kötüye şöyle sıralanır: tam uyum, tamsayı ya da
kayan nokta terfisi, standart dönüşüm, kullanıcı tanımlı dönüşüm ve en sonda üç noktalı
parametre. Bir aday her argümanda diğerlerinden kötü değilse ve en az bir argümanda daha
iyiyse en iyidir. İki aday birbirini yenemiyorsa çağrı belirsizdir ve derleme durur; bu hatayı
düzyazıda anlatıyorum, çünkü derlenmeyen kod blok olarak yazılmaz.

```cpp
// siralama.cpp — donusum siralamasi
#include <cstdio>

const char* h(int) { return "int"; }
const char* h(long) { return "long"; }
const char* h(double) { return "double"; }

const char* g(long) { return "long"; }
const char* g(...) { return "uc nokta"; }

int main() {
    short s = 1;
    std::printf("h('a')  %s\n", h('a'));
    std::printf("h(s)    %s\n", h(s));
    std::printf("h(1.5f) %s\n", h(1.5f));
    std::printf("h(2L)   %s\n", h(2L));
    std::printf("g(1)    %s\n", g(1));
    std::printf("g(1.5)  %s\n", g(1.5));
    return 0;
}
```

```text
h('a')  int
h(s)    int
h(1.5f) double
h(2L)   long
g(1)    long
g(1.5)  long
```

**MP27.** `char` ve `short` argümanları `int`'e terfiyle bağlanır ve terfi, `long`'a standart
dönüşümden iyidir; bu yüzden ikisi de `int` adayını seçer. `float`, `double`'a terfiyle
bağlanır. `2L` için `long` tam uyumdur. `g(1)` için `long`'a standart dönüşüm ile üç nokta
yarışır: dönüşüm kazanır. `g(1.5)` için de `double`'dan `long`'a standart dönüşüm vardır
ve üç noktadan iyidir. Bu sıralamayı belirleyen standarttır; çıktı yalnız onu doğrular.

## Öncelik Etiketi ile Aday Sıralamak

`enable_if` ile elenen adayların birden fazlası aynı anda uygunsa çağrı belirsizleşir.
Öncelik kurmak için eski bir yöntem, etiket tiplerinden bir türetme zinciri kurmaktır.
`oncelik<2>`, `oncelik<1>`'den, o da `oncelik<0>`'dan türer. Her aday son parametre olarak
farklı bir etiket alır ve çağrı en yüksek etiketle yapılır: tam uyan en yüksek etikettir,
elenenler listeden düşer, kalanlar arasından türetme mesafesi en kısa olan seçilir.

```cpp
// oncelik.cpp — etiketle aday sirasi
#include <cstdio>
#include <string>
#include <type_traits>
#include <utility>

template <int N>
struct oncelik : oncelik<N - 1> {};
template <>
struct oncelik<0> {};

template <typename T>
auto ayir(const T& x, oncelik<2>) -> decltype(x.size(), "boyutlu") {
    return "boyutlu";
}

template <typename T, std::enable_if_t<std::is_pointer_v<T>, int> = 0>
const char* ayir(const T&, oncelik<1>) { return "isaretci"; }

template <typename T>
const char* ayir(const T&, oncelik<0>) { return "genel"; }

template <typename T>
const char* ayir(const T& x) { return ayir(x, oncelik<2>{}); }

int main() {
    int n = 3;
    std::printf("string  %s\n", ayir(std::string("ab")));
    std::printf("isaretci %s\n", ayir(&n));
    std::printf("int     %s\n", ayir(n));
    return 0;
}
```

```text
string  boyutlu
isaretci isaretci
int     genel
```

**MP28.** `std::string` için iki aday uygundur, çünkü işaretçi adayı `enable_if` ile
elenir; `oncelik<2>` en yüksek etikettir ve bir
türetme adımı gerektirmez, `boyutlu` kazanır. `int*` için `size()` sorgusu bildirimde
başarısız olur, ilk aday elenir ve `oncelik<1>` adayı seçilir. `int` için ikisi de
elenir ve `genel` kalır. C++17 `if constexpr` ve C++20 kavramları bu zincirin yerini alır;
ama etiket yöntemi eski kodda hâlâ yaygındır, o yüzden okuyabilmek gerekir.

## void_t ile Algılama

`enable_if` bir koşulu açıkça yazar. Bazen soru, bir ifadenin tip için geçerli olup
olmadığıdır. `std::void_t<...>` bu soruyu SFINAE'ye bağlar: argümanları ne olursa olsun
`void` olur, ama argümanlardan biri geçersizse yerine koyma başarısız olur. Genel şablon
`false_type`'tan türer; kısmi özelleştirme `void_t` içinde ifadeyi dener.

```cpp
// bolunur.cpp — void_t ile algilama
#include <cstdio>
#include <string>
#include <type_traits>
#include <utility>

template <typename T, typename = void>
struct bolunur : std::false_type {};

template <typename T>
struct bolunur<T, std::void_t<decltype(std::declval<T>() / 2)>> : std::true_type {};

int main() {
    std::printf("bolunur<int>         %s\n", bolunur<int>::value ? "evet" : "hayir");
    std::printf("bolunur<double>      %s\n", bolunur<double>::value ? "evet" : "hayir");
    std::printf("bolunur<std::string> %s\n", bolunur<std::string>::value ? "evet" : "hayir");
    return 0;
}
```

```text
bolunur<int>         evet
bolunur<double>      evet
bolunur<std::string> hayir
```

`std::declval<T>() / 2` ifadesi `int` için geçerlidir, `std::string` için geçersizdir.
Geçersizse kısmi özelleştirme elenir ve genel şablon kalır. Bu, ders başında sorulan
soruya cevaptır: uygun olmayan aday hata vermez, aday listesinden düşer.

## C++20: Kavram ve requires

`enable_if` ve `void_t` işi görür ama okunması zordur. C++20, aynı kısıtı doğrudan
yazmak için **kavram** ve `requires` ifadesi getirdi. Bir **kısıt** sağlanmıyorsa şablon
aday olmaz, derleyiciler de hata iletisinde genellikle uzun bir yerine koyma dökümü yerine sağlanmayan
kısıtı gösterir.

```cpp
// kavram.cpp — requires ile kisit
#include <cstdio>
#include <string>

template <typename T>
concept Bolunebilir = requires(T x) { x / 2; };

template <Bolunebilir T>
const char* yaris(T) { return "bolunur"; }

const char* yaris(...) { return "bolunmez"; }

int main() {
    std::printf("yaris(8)   %s\n", yaris(8));
    std::printf("yaris(str) %s\n", yaris(std::string("x")));
    return 0;
}
```

```text
yaris(8)   bolunur
yaris(str) bolunmez
```

Kısıtlı şablon `int` için uygun adaydır ve üç noktalı aday daha az iyi olduğu için
kazanır. `string` için kısıt sağlanmaz, şablon elenir ve geriye üç noktalı aday kalır.
Kavram bir sorguyu adlandırır; seçim yine derlemede biter.

## Elenen Adayın Gövdesi Yazılmaz

SFINAE'nin bedelinin neresinde olduğunu sayıyla görelim. Bir aday elenirse, gövdesi
hiçbir zaman somutlaştırılmaz. Aşağıda iki adayın gövdesi yerel bir `static` ile kayıt
altına alınır; çağrılmayan gövde kayıt bırakmaz.

```cpp
// govde-sayisi.cpp — elenen adayin govdesi
#include <cstdio>
#include <type_traits>

inline int tamsayi_govde = 0;
inline int kayan_govde = 0;

template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
int ikiye(T x) {
    static const bool kayit = (++tamsayi_govde, true);
    (void)kayit;
    return static_cast<int>(x) * 2;
}

template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
int ikiye(T x) {
    static const bool kayit = (++kayan_govde, true);
    (void)kayit;
    return static_cast<int>(x * 2);
}

int main() {
    ikiye(7);
    ikiye(8);
    ikiye(9L);
    std::printf("tamsayi=%d kayan=%d\n", tamsayi_govde, kayan_govde);
    ikiye(2.5);
    std::printf("tamsayi=%d kayan=%d\n", tamsayi_govde, kayan_govde);
    return 0;
}
```

```text
tamsayi=2 kayan=0
tamsayi=2 kayan=1
```

**MP29.** Üç tamsayı çağrısından sonra yalnız tamsayı gövdeleri vardır: `int` için bir,
`long` için bir, yani iki gövde; kayan gövde sıfırdır. Sonra bir `double` çağrısı kayan
gövdeyi bir yapar. Elenen aday listeden düşmüştür ve gövdesi hiç yazılmamıştır; bu yüzden
SFINAE'nin bedeli yalnız elemenin kendisidir. Sayılar bir ürün sayısıdır, bir süre
değil.

## Kavramlarda Sıralama: Kapsama

Kavramlarla çalışan C++20 çözümlemesi, aynı adayın iki kısıtlı sürümü uygunsa, daha çok
kısıtlananı seçer. Bir kavram bir başkasını içeriyorsa (`std::signed_integral`,
`std::integral`'ı kapsar) kapsayan kavram daha özeldir. Bu kural, etiket zincirinin
yerini alır: sıra, kavramların kendi yapısından çıkar.

```cpp
// kisit-sira.cpp — kavram kapsamasi
#include <concepts>
#include <cstdio>

template <typename T>
const char* sinif(T) { return "genel"; }

template <std::integral T>
const char* sinif(T) { return "tamsayi"; }

template <std::signed_integral T>
const char* sinif(T) { return "isaretli"; }

int main() {
    std::printf("sinif(1)   %s\n", sinif(1));
    std::printf("sinif(1u)  %s\n", sinif(1u));
    std::printf("sinif(1.5) %s\n", sinif(1.5));
    return 0;
}
```

```text
sinif(1)   isaretli
sinif(1u)  tamsayi
sinif(1.5) genel
```

**MP30.** `int` hem `integral` hem `signed_integral`'dır; daha çok kısıtlı olan `isaretli`
seçilir. `unsigned` yalnız `integral`'dır, `tamsayi` kalır. `double` hiçbirini sağlamaz,
`genel` kalır. Bu seçim yine derlemededir ve çağrı noktasında bir sorgu bırakmaz. Bedel
**derleme** sınıfındadır; yalnız adayların toplanması ve kısıtların denetlenmesi
derleyicinin işine eklenir.

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

SFINAE yalnız bildirimin **hemen bağlamındaki** yerine koyma hatalarını kapsar: dönüş
tipi, parametre tipleri ve şablon parametreleri yazılırken doğrudan çıkan hatalar. Bu
sırada başka bir şablonun somutlaştırılması gerekir ve hata onun içinde çıkarsa, eleme
olmaz, derleme durur. İşlevin gövdesi aday seçiminden sonra somutlaştırılır. Gövdedeki
bir hata artık aday listesinden eleme yapmaz, derlemeyi durdurur. Örneğin `x.size()`
çağrısını gövdede yazan bir şablon `int` ile çağrılırsa, derleyici o şablonu uygun aday
sayar, seçer, gövdeyi somutlaştırırken hata verir ve derleme durur.

Aynı çağrı bildirime taşınınca elemeye dönüşür. Aşağıdaki sürüm, `size()` sorgusunu
dönüş tipinde yapar.

```cpp
// bildirim.cpp — sorgu bildirimde, gövde hatasiz
#include <cstdio>
#include <string>

template <typename T>
auto boyut(const T& x) -> decltype(x.size(), long()) {
    return static_cast<long>(x.size());
}

long boyut(...) { return -1; }

int main() {
    std::printf("string %ld\n", boyut(std::string("abcd")));
    std::printf("int    %ld\n", boyut(7));
    return 0;
}
```

```text
string 4
int    -1
```

**MP31.** `string` için dönüş tipi geçerlidir ve şablon seçilir. `int` için `x.size()`
geçersizdir, yerine koyma başarısız olur ve üç noktalı aday kalır. Aynı sorgu gövdede
olsaydı derleme dururdu. Bu yüzden sayı eleme olayının kanıtıdır; ölçü sorusunun yanıtı bu
elemenin yapıldığı aşamadır: **derleme**.

## Özet

- Bu dersin ölçtüğü düzenek bedeli **derleme** sınıfına taşır: aday toplama, uygun
  adayı eleme ve en iyi adayı seçme derlemede biter, koşumda sorgu kalmaz.
- Aday, uygun aday ve en iyi aday sırasıyla toplanır, elenir ve seçilir; eşitlikte şablon
  olmayan kazanır, tam uyum dönüşümü yener.
- SFINAE'de yerine koyma başarısızlığı hata değildir: `enable_if_t` ile
  `tur(7)` tamsayi, `tur(2.5)` kayan seçilir.
- `void_t` bir ifadenin geçerliliğini algılar: `bolunur<int>` evet, `bolunur<std::string>`
  hayir.
- C++20 `concept` ve `requires` aynı kısıtı okunur yazar; SFINAE yalnız bildirimi kapsar,
  gövdedeki hata derlemeyi durdurur.

## Sonraki Adım

Bu derste aday elemesi derlemede bitti ve koşumda hiçbir sorgu bırakmadı. Sıradaki ders
bu fikri bir sınıf hiyerarşisine uyguluyor: sanal işlev yerine şablon kullanılırsa
gönderim derlemeye taşınır, ama her tip için ayrı gövde yazdırılır. Bu değiş tokuşun
ölçüsü ne?
