← Kursa Dön
📄 Text · 20 min

Type Traits, SFINAE ve Concepts Geçişi

Bir hastane acil servisini düşün. Hasta geldiğinde triaj hemşiresi hastayı değerlendirir — kalp krizi mi, kırık mı, alerji mi? Hastanın tipine göre doğru bölüme yönlendirir. Kalp krizini ortopediye, kırığı kardiyolojiye göndermez. Bu bir derleme zamanı tip kontrolü analojisidir. C++ template'leri de benzer şekilde çalışır: gelen tipin özelliklerine bakarak doğru kodu seçer, yanlış tipi reddeder — hepsi derleme zamanında, runtime maliyeti sıfır.

Bu derste C++'ın tip sistemiyle derleme zamanında sorgulama, filtreleme ve karar verme mekanizmalarını öğreneceğiz. type_traits kütüphanesiyle tiplerin özelliklerini sorgulayacağız, SFINAE ile template overload seçimini kontrol edeceğiz ve C++20 Concepts ile tüm bunları temiz, okunabilir bir syntax'a taşıyacağız.


Type Traits: Tiplerin Kimlik Kartı

type_traits Kütüphanesi

<type_traits> başlık dosyası, bir tipin özelliklerini derleme zamanında sorgulamayı sağlayan araçlar sunar. "Bu tip tam sayı mı?", "Bu tip kopyalanabilir mi?", "Bu iki tip aynı mı?" gibi soruları compile-time'da cevaplayabilirsin:

#include <type_traits>
#include <iostream>
#include <string>

int main() {
    // Tip sorgulama
    std::cout << std::boolalpha;
    
    std::cout << "int tam sayi mi? " 
              << std::is_integral_v<int> << std::endl;        // true
    
    std::cout << "double tam sayi mi? " 
              << std::is_integral_v<double> << std::endl;     // false
    
    std::cout << "float ondalikli mi? " 
              << std::is_floating_point_v<float> << std::endl; // true
    
    std::cout << "int ve int ayni mi? " 
              << std::is_same_v<int, int> << std::endl;        // true
    
    std::cout << "int ve long ayni mi? " 
              << std::is_same_v<int, long> << std::endl;       // false
    
    // const/volatile kontrol
    std::cout << "const int const mu? " 
              << std::is_const_v<const int> << std::endl;      // true
    
    // Pointer kontrol
    std::cout << "int* pointer mi? " 
              << std::is_pointer_v<int*> << std::endl;         // true
}

_v suffix'i C++17 ile gelen kısayoldur: std::is_integral_v<T>, std::is_integral<T>::value ile aynı şeydir. Eski kodlarda ::value formunu görebilirsin ama yeni kodda _v tercih edilir.

Yaygın Type Traits

TraitNe SorarÖrnek (true)
is_integralTam sayı mı?int, char, bool, long
is_floating_pointOndalıklı sayı mı?float, double
is_arithmeticSayısal mı?Integral + floating point
is_same<A,B>A ve B aynı tip mi?is_same_v<int, int>
is_base_of<B,D>B, D'nin base class'ı mı?Kalıtım kontrolü
is_constructible<T,Args>T, Args ile oluşturulabilir mi?Constructor kontrolü
is_copy_constructibleKopyalanabilir mi?unique_ptr → false
is_trivially_copyablememcpy ile kopyalanabilir mi?POD tipler
is_voidvoid mı?is_void_v<void>
is_referenceReferans mı?int&, int&&

Tip Dönüşüm Traits

Bazı traits sadece sorgulamaz, tipi dönüştürür:

#include <type_traits>
#include <iostream>

int main() {
    // const kaldır
    using A = std::remove_const_t<const int>;  // int
    
    // Referans kaldır
    using B = std::remove_reference_t<int&>;   // int
    
    // Pointer ekle
    using C = std::add_pointer_t<int>;         // int*
    
    // const ekle
    using D = std::add_const_t<int>;           // const int
    
    // Decay: array → pointer, function → pointer, const/ref kaldır
    using E = std::decay_t<const int&>;        // int
    using F = std::decay_t<int[5]>;            // int*
    
    std::cout << std::boolalpha;
    std::cout << std::is_same_v<A, int> << std::endl;    // true
    std::cout << std::is_same_v<C, int*> << std::endl;   // true
}

_t suffix'i ::type kısayoludur: std::remove_const_t<T>, typename std::remove_const<T>::type ile aynı. C++14 ile geldi ve modern kodda standart.


std::conditional ve std::enable_if

std::conditional — Derleme Zamanı if/else

std::conditional, bir boolean koşula göre iki tipten birini seçer. Runtime if'i değil, compile-time type selection'dır:

#include <type_traits>
#include <cstdint>
#include <iostream>

// Platform bağımsız sayı tipi seçimi
template <bool Is64Bit>
using PlatformInt = std::conditional_t<Is64Bit, int64_t, int32_t>;

// Örnek: boyuta göre en uygun tip
template <size_t N>
using SmallestInt = std::conditional_t<(N <= 8), int8_t,
                    std::conditional_t<(N <= 16), int16_t,
                    std::conditional_t<(N <= 32), int32_t,
                    int64_t>>>;

int main() {
    PlatformInt<true> buyuk = 9'000'000'000;   // int64_t
    PlatformInt<false> kucuk = 42;              // int32_t
    
    SmallestInt<7> a = 100;    // int8_t
    SmallestInt<15> b = 1000;  // int16_t
    SmallestInt<31> c = 50000; // int32_t
    
    std::cout << "sizeof(a) = " << sizeof(a) << std::endl;  // 1
    std::cout << "sizeof(b) = " << sizeof(b) << std::endl;  // 2
    std::cout << "sizeof(c) = " << sizeof(c) << std::endl;  // 4
}

std::enable_if — Template'i Koşullu Etkinleştir

enable_if, bir koşul sağlandığında template'in var olmasını, sağlanmadığında sanki hiç tanımlanmamış gibi davranmasını sağlar:

#include <type_traits>
#include <iostream>
#include <string>

// Sadece tam sayılar için çalışan fonksiyon
template <typename T>
std::enable_if_t<std::is_integral_v<T>, T>
guvenliTopla(T a, T b) {
    // Overflow kontrolü
    if (a > 0 && b > std::numeric_limits<T>::max() - a) {
        throw std::overflow_error("Toplama overflow!");
    }
    return a + b;
}

// Sadece ondalıklı sayılar için çalışan fonksiyon
template <typename T>
std::enable_if_t<std::is_floating_point_v<T>, T>
guvenliTopla(T a, T b) {
    return a + b;  // Float'ta overflow farklı davranır
}

int main() {
    std::cout << guvenliTopla(3, 4) << std::endl;        // int versiyon: 7
    std::cout << guvenliTopla(3.14, 2.71) << std::endl;  // float versiyon: 5.85
    
    // guvenliTopla(std::string("a"), std::string("b"));
    // Derleme hatası: ne integral ne floating_point
}

enable_if koşul false olduğunda template'i devre dışı bırakır. Compiler diğer overload'lara bakar veya uygun bir overload bulamazsa derleme hatası verir. Bu mekanizmaya SFINAE denir.


SFINAE: Substitution Failure Is Not An Error

SFINAE Nedir?

SFINAE, C++ template çözümleme sürecinin temel kuralıdır. Template argümanları yerine konulduğunda (substitution) bir hata oluşursa, compiler hata vermez — sadece o template'i aday listesinden çıkarır ve diğer adaylara bakar.

Bunu triaj hemşiresine benzetebilirsin: hasta (argüman) kardiyolojiye (template) uygun değilse, hemşire "bu hasta burada tedavi edilemez" deyip hastaneyi kapatmaz — sadece başka bölüme yönlendirir.

#include <type_traits>
#include <iostream>
#include <vector>
#include <string>

// Overload 1: .size() metodu olan tipler için
template <typename T>
auto elemanSayisi(const T& container) 
    -> decltype(container.size(), size_t{}) 
{
    return container.size();
}

// Overload 2: C-array'ler için
template <typename T, size_t N>
size_t elemanSayisi(const T (&)[N]) {
    return N;
}

// Overload 3: Diğer herşey
size_t elemanSayisi(...) {
    return 0;  // Bilinmiyor
}

int main() {
    std::vector<int> v = {1, 2, 3};
    std::string s = "merhaba";
    int arr[] = {10, 20, 30, 40};
    
    std::cout << elemanSayisi(v) << std::endl;    // 3 (Overload 1)
    std::cout << elemanSayisi(s) << std::endl;    // 7 (Overload 1)
    std::cout << elemanSayisi(arr) << std::endl;  // 4 (Overload 2)
    std::cout << elemanSayisi(42) << std::endl;   // 0 (Overload 3)
}

İlk overload container.size() ifadesini dener. int için .size() yoktur → substitution failure → bu overload elenir → compiler array versiyonuna bakar → o da uymaz → variadic fallback çalışır. Hiçbir yerde derleme hatası oluşmaz çünkü SFINAE devrede.

SFINAE ile Template Overload Seçimi

Daha karmaşık bir örnek — bir fonksiyonun farklı tiplere göre farklı davranması:

#include <type_traits>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

// Stringe dönüştürme: aritmetik tipler
template <typename T>
std::enable_if_t<std::is_arithmetic_v<T>, std::string>
toString(const T& val) {
    return std::to_string(val);
}

// Stringe dönüştürme: string tipi (zaten string)
template <typename T>
std::enable_if_t<std::is_same_v<std::decay_t<T>, std::string>, std::string>
toString(const T& val) {
    return val;
}

// Stringe dönüştürme: << operatörü olan tipler (genel)
template <typename T>
auto toString(const T& val) 
    -> std::enable_if_t<
        !std::is_arithmetic_v<T> && 
        !std::is_same_v<std::decay_t<T>, std::string>,
        decltype(std::declval<std::ostream&>() << val, std::string{})
    >
{
    std::ostringstream oss;
    oss << val;
    return oss.str();
}

int main() {
    std::cout << toString(42) << std::endl;              // "42"
    std::cout << toString(3.14) << std::endl;            // "3.140000"
    std::cout << toString(std::string("hello")) << std::endl;  // "hello"
}

Bu çalışır ama okunması gerçekten zor. enable_if zincirleri, decltype ifadeleri, declval kullanımı — SFINAE doğru ama çirkin bir tekniktir. İşte C++20 Concepts'in var olma nedeni bu.


static_assert: Derleme Zamanı Kontrol

static_assert derleme zamanında bir koşulu kontrol eder ve koşul sağlanmazsa anlaşılır bir hata mesajı ile derlemeyi durdurur:

#include <type_traits>
#include <cstdint>

template <typename T>
class SayisalBuffer {
    static_assert(std::is_arithmetic_v<T>, 
                  "SayisalBuffer sadece sayisal tiplerle kullanilabilir!");
    static_assert(sizeof(T) <= 8, 
                  "Tip boyutu 8 byte'i gecemez!");
    
    T* data_;
    size_t boyut_;
    
public:
    explicit SayisalBuffer(size_t n) 
        : data_(new T[n]), boyut_(n) {}
    
    ~SayisalBuffer() { delete[] data_; }
    
    T& operator[](size_t i) { return data_[i]; }
    size_t size() const { return boyut_; }
};

// Kullanım
SayisalBuffer<int> buf1(100);     // OK
SayisalBuffer<double> buf2(50);   // OK

// SayisalBuffer<std::string> buf3(10);
// Derleme hatası: "SayisalBuffer sadece sayisal tiplerle kullanilabilir!"

static_assert, SFINAE'den farklı olarak overload seçimi yapmaz, sadece derlemeyi durdurur. Amacı geliştiriciyi erken ve anlaşılır biçimde uyarmaktır. Template sınıflarında "bu tip burada kullanılamaz" demek için idealdir.

static_assert Pratik Kullanımlar

#include <type_traits>

// Platform varsayımlarını doğrula
static_assert(sizeof(int) >= 4, "int en az 4 byte olmali!");
static_assert(sizeof(void*) == 8, "64-bit platform gerekli!");

// Struct layout kontrolü
struct NetworkPacket {
    uint32_t header;
    uint16_t length;
    uint16_t checksum;
    // ... data ...
};

static_assert(std::is_trivially_copyable_v<NetworkPacket>,
              "NetworkPacket memcpy ile kopyalanabilir olmali!");

static_assert(sizeof(NetworkPacket) == 8,
              "NetworkPacket tam 8 byte olmali — padding var mi kontrol et!");

C++20 Concepts: Temiz Sözdizimi

SFINAE'nin Problemi

SFINAE çalışır ama üç büyük sorunu var:

  1. Okunabilirlik: enable_if_t<is_integral_v<T> && !is_same_v<T, bool>, T> gibi ifadeler göz yakar

  2. Hata mesajları: Koşul sağlanmadığında compiler sayfalarca template hata mesajı verir

  3. Bakım zorluğu: Birden fazla SFINAE constraint birleştiğinde çözülmesi imkansız hale gelir

C++20 Concepts bu üç sorunu birden çözer:

#include <concepts>
#include <iostream>

// SFINAE versiyonu (çirkin)
template <typename T>
std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, T>
ciftMi_sfinae(T n) {
    return n % 2 == 0;
}

// Concepts versiyonu (temiz)
template <std::integral T>
bool ciftMi_concepts(T n) requires (!std::same_as<T, bool>) {
    return n % 2 == 0;
}

// Hatta daha kısa
bool ciftMi_kisa(std::integral auto n) {
    return n % 2 == 0;
}

int main() {
    std::cout << ciftMi_concepts(42) << std::endl;   // true
    std::cout << ciftMi_kisa(7) << std::endl;         // false
    
    // ciftMi_concepts(3.14);  
    // Hata: "constraint not satisfied: std::integral<double>"
    // SFINAE'deki sayfalık hataya karşılık, tek satır!
}

Concept Nedir?

Concept, bir template parametresinin karşılaması gereken gereksinimleri tanımlayan bir compile-time predicate'tir. Boolean bir ifade gibi düşün — true ise template kullanılabilir, false ise kullanılamaz:

#include <concepts>
#include <type_traits>

// std::integral kavramının tanımı (standart kütüphanede böyle)
// template <typename T>
// concept integral = std::is_integral_v<T>;

// std::floating_point
// template <typename T>
// concept floating_point = std::is_floating_point_v<T>;

// std::same_as
// template <typename T, typename U>
// concept same_as = std::is_same_v<T, U> && std::is_same_v<U, T>;

Concept Kullanım Yolları

Bir concept'i template'e uygulamanın dört yolu var:

#include <concepts>
#include <iostream>

// 1. Template parametre constraint
template <std::integral T>
T yol1(T a, T b) { return a + b; }

// 2. requires clause (fonksiyon sonrası)
template <typename T>
T yol2(T a, T b) requires std::integral<T> { return a + b; }

// 3. requires clause (template sonrası)
template <typename T> requires std::integral<T>
T yol3(T a, T b) { return a + b; }

// 4. Abbreviated template (en kısa)
auto yol4(std::integral auto a, std::integral auto b) { return a + b; }

int main() {
    std::cout << yol1(3, 4) << std::endl;     // 7
    std::cout << yol4(10, 20) << std::endl;    // 30
}

Dört yol da aynı şeyi yapar. Basit constraint'ler için yol 1 veya 4, karmaşık constraint'ler için yol 2 veya 3 tercih edilir.


requires Clause ve requires Expression

requires Clause

requires keyword'ü bir boolean constraint ifadesi alır:

#include <concepts>
#include <iostream>

template <typename T>
requires (sizeof(T) <= 8) && std::is_trivially_copyable_v<T>
void hizliKopya(T* hedef, const T* kaynak, size_t n) {
    std::memcpy(hedef, kaynak, n * sizeof(T));
}

// Birden fazla constraint birleştirilebilir
template <typename T>
requires std::integral<T> || std::floating_point<T>
T mutlakDeger(T val) {
    return val < 0 ? -val : val;
}

int main() {
    std::cout << mutlakDeger(-42) << std::endl;    // 42
    std::cout << mutlakDeger(-3.14) << std::endl;  // 3.14
}

requires Expression

requires aynı zamanda bir expression olarak kullanılabilir — "bu ifadeler geçerli mi?" sorusunu sorar:

#include <concepts>
#include <iostream>
#include <string>
#include <vector>

// "T'nin .size() metodu var mı ve size_t döndürüyor mu?"
template <typename T>
concept HasSize = requires(T t) {
    { t.size() } -> std::convertible_to<size_t>;
};

// "T yazdırılabilir mi?"
template <typename T>
concept Printable = requires(T t, std::ostream& os) {
    { os << t } -> std::same_as<std::ostream&>;
};

// "T aritmetik işlemleri destekliyor mu?"
template <typename T>
concept Arithmetic = requires(T a, T b) {
    { a + b } -> std::convertible_to<T>;
    { a - b } -> std::convertible_to<T>;
    { a * b } -> std::convertible_to<T>;
    { a / b } -> std::convertible_to<T>;
};

// Kullanım
template <HasSize T>
void boyutYazdir(const T& container) {
    std::cout << "Boyut: " << container.size() << std::endl;
}

template <Printable T>
void yazdir(const T& val) {
    std::cout << val << std::endl;
}

int main() {
    std::vector<int> v = {1, 2, 3};
    std::string s = "merhaba";
    
    boyutYazdir(v);  // Boyut: 3
    boyutYazdir(s);  // Boyut: 7
    
    yazdir(42);           // 42
    yazdir("test");       // test
    yazdir(std::string("hello"));  // hello
}

requires expression dört tür gereksinim tanımlayabilir:

TürSözdizimiAnlamı
Simplet.foo();İfade geçerli mi?
Typetypename T::value_type;Tip var mı?
Compound{ t.foo() } -> concept;İfade geçerli mi VE sonuç tipi constraint'i sağlıyor mu?
Nestedrequires std::integral<T>;İç içe constraint

Standart Concepts

C++20 <concepts> başlık dosyasında kullanıma hazır birçok concept sunar:

#include <concepts>
#include <ranges>
#include <iostream>
#include <vector>
#include <list>

// Temel tip concepts
template <std::integral T>
void tamSayi(T) { std::cout << "Tam sayi!" << std::endl; }

template <std::floating_point T>
void ondalikli(T) { std::cout << "Ondalikli!" << std::endl; }

// Karşılaştırma concepts
template <std::totally_ordered T>
T maksimum(T a, T b) { return a > b ? a : b; }

// Range concept
template <std::ranges::range R>
void elemanSay(const R& r) {
    size_t sayac = 0;
    for (auto it = std::ranges::begin(r); it != std::ranges::end(r); ++it) {
        ++sayac;
    }
    std::cout << "Eleman sayisi: " << sayac << std::endl;
}

// Callable concepts
template <std::invocable<int> F>
void uygula(F f, int val) {
    f(val);
}

int main() {
    tamSayi(42);        // Tam sayi!
    ondalikli(3.14);    // Ondalikli!
    
    std::cout << maksimum(3, 7) << std::endl;       // 7
    std::cout << maksimum(3.14, 2.71) << std::endl;  // 3.14
    
    std::vector<int> v = {1, 2, 3, 4, 5};
    std::list<double> l = {1.1, 2.2, 3.3};
    elemanSay(v);  // Eleman sayisi: 5
    elemanSay(l);  // Eleman sayisi: 3
    
    uygula([](int n) { std::cout << n * 2 << std::endl; }, 21);  // 42
}

Yaygın Standart Concepts

ConceptBaşlıkNe Kontrol Eder
std::integral<concepts>Tam sayı tipi mi?
std::floating_point<concepts>Ondalıklı tip mi?
std::same_as<T,U><concepts>T ve U aynı tip mi?
std::convertible_to<From,To><concepts>From → To dönüşümü var mı?
std::totally_ordered<concepts><, >, <=, >= destekliyor mu?
std::copyable<concepts>Kopyalanabilir mi?
std::movable<concepts>Taşınabilir mi?
std::invocable<F,Args...><concepts>F(Args...) çağrılabilir mi?
std::ranges::range<ranges>begin/end sağlıyor mu?
std::ranges::input_range<ranges>Okunabilir range mi?
std::ranges::random_access_range<ranges>Rastgele erişim var mı?

Custom Concept Tanımlama

Basit Concepts

#include <concepts>
#include <iostream>
#include <string>
#include <cmath>

// Sayısal tip — integral veya floating point
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;

// String-like — std::string veya const char*
template <typename T>
concept StringLike = std::same_as<std::decay_t<T>, std::string> || 
                     std::same_as<std::decay_t<T>, const char*>;

// Kullanım
template <Numeric T>
T kpiHesapla(T gerceklesen, T hedef) {
    if (hedef == 0) return 0;
    return (gerceklesen * 100) / hedef;
}

template <StringLike T>
void selamla(T isim) {
    std::cout << "Merhaba, " << isim << "!" << std::endl;
}

int main() {
    std::cout << kpiHesapla(85, 100) << std::endl;      // 85
    std::cout << kpiHesapla(3.7, 5.0) << std::endl;     // 74.0
    
    selamla("Ali");                     // const char*
    selamla(std::string("Ayse"));       // std::string
}

Karmaşık Concepts

#include <concepts>
#include <iostream>
#include <string>

// Serializable: serialize/deserialize yeteneği olan tipler
template <typename T>
concept Serializable = requires(T t, std::string s) {
    { t.serialize() } -> std::convertible_to<std::string>;
    { T::deserialize(s) } -> std::same_as<T>;
};

// Container concept: STL-uyumlu container
template <typename T>
concept Container = requires(T t) {
    typename T::value_type;
    typename T::iterator;
    { t.begin() } -> std::same_as<typename T::iterator>;
    { t.end() } -> std::same_as<typename T::iterator>;
    { t.size() } -> std::convertible_to<size_t>;
    { t.empty() } -> std::same_as<bool>;
};

// Hashable: std::hash ile hash'lenebilen tipler
template <typename T>
concept Hashable = requires(T t) {
    { std::hash<T>{}(t) } -> std::convertible_to<size_t>;
};

// Kullanım: Sadece Container olan şeyleri kabul et
template <Container C>
void ozet(const C& c) {
    std::cout << "Tip: " << typeid(C).name() 
              << ", Boyut: " << c.size() 
              << ", Bos mu: " << std::boolalpha << c.empty() 
              << std::endl;
}

// Serializable implementasyonu
struct Kullanici {
    std::string isim;
    int yas;
    
    std::string serialize() const {
        return isim + ":" + std::to_string(yas);
    }
    
    static Kullanici deserialize(const std::string& s) {
        auto pos = s.find(':');
        return {s.substr(0, pos), std::stoi(s.substr(pos + 1))};
    }
};

template <Serializable T>
void kaydet(const T& obj) {
    std::string veri = obj.serialize();
    std::cout << "Kaydedildi: " << veri << std::endl;
}

int main() {
    std::vector<int> v = {1, 2, 3};
    ozet(v);  // Boyut: 3, Bos mu: false
    
    Kullanici k{"Ali", 25};
    kaydet(k);  // Kaydedildi: Ali:25
    
    auto k2 = Kullanici::deserialize("Ayse:30");
    std::cout << k2.isim << ", " << k2.yas << std::endl;  // Ayse, 30
}

💡 İpucu: Custom concept tanımlarken minimal ol. "Bu template parametresi gerçekten neye ihtiyaç duyuyor?" sorusunu sor. Gereksiz yere fazla constraint ekleme — bu template'in kullanılabilirliğini kısıtlar. Bir fonksiyon sadece begin/end gerektiriyorsa tüm Container concept'ini talep etme, std::ranges::range yeterli.


SFINAE → Concepts Migration Rehberi

Dönüşüm Tablosu

SFINAEConcepts Karşılığı
enable_if_t<is_integral_v<T>>std::integral T veya requires std::integral<T>
enable_if_t<is_floating_point_v<T>>std::floating_point T
enable_if_t<is_same_v<T, U>>std::same_as<T, U>
decltype(expr, void())requires { expr; }
decltype(expr) dönüş tipi{ expr } -> concept
void_t<...>requires { typename ...; }

Pratik Migration Örnekleri

#include <concepts>
#include <type_traits>
#include <iostream>
#include <vector>

// ===== ÖNCE: SFINAE =====

// enable_if ile
template <typename T, 
          typename = std::enable_if_t<std::is_integral_v<T>>>
T ikiKati_v1(T n) { return n * 2; }

// Trailing return type SFINAE
template <typename T>
auto boyut_v1(const T& c) -> decltype(c.size()) {
    return c.size();
}

// Karmaşık SFINAE: printable + has size
template <typename T,
          typename = std::enable_if_t<
              std::is_same_v<
                  decltype(std::declval<std::ostream&>() << std::declval<T>()),
                  std::ostream&
              >>,
          typename = decltype(std::declval<T>().size())>
void bilgiYazdir_v1(const T& obj) {
    std::cout << obj << " (boyut: " << obj.size() << ")" << std::endl;
}


// ===== SONRA: CONCEPTS =====

// Concepts ile — temiz!
template <std::integral T>
T ikiKati_v2(T n) { return n * 2; }

// Concepts + requires expression
template <typename T>
requires requires(const T& c) { { c.size() } -> std::convertible_to<size_t>; }
auto boyut_v2(const T& c) { return c.size(); }

// Karmaşık constraint — yine de okunabilir
template <typename T>
concept PrintableWithSize = requires(T t, std::ostream& os) {
    { os << t } -> std::same_as<std::ostream&>;
    { t.size() } -> std::convertible_to<size_t>;
};

template <PrintableWithSize T>
void bilgiYazdir_v2(const T& obj) {
    std::cout << obj << " (boyut: " << obj.size() << ")" << std::endl;
}


int main() {
    // Her iki versiyon da aynı çalışır
    std::cout << ikiKati_v1(21) << std::endl;   // 42
    std::cout << ikiKati_v2(21) << std::endl;   // 42
    
    std::vector<int> v = {1, 2, 3};
    std::cout << boyut_v1(v) << std::endl;      // 3
    std::cout << boyut_v2(v) << std::endl;      // 3
    
    std::string s = "test";
    bilgiYazdir_v2(s);  // test (boyut: 4)
}

Migration Stratejisi

  1. Yeni kodda Concepts kullan. Hiçbir neden yokken SFINAE yazma.

  2. Mevcut SFINAE'yi hemen değiştirme. Çalışan koda dokunma — "if it ain't broke, don't fix it."

  3. Kademeli geçiş yap. Yeni overload eklediğinde veya refactor yaparken Concepts'e geçir.

  4. static_assert'ları koru. Concepts overload seçimi yapar ama static_assert özel hata mesajı verir — ikisi farklı amaçlara hizmet eder.

  5. Compiler uyumluluğunu kontrol et. Concepts için C++20 desteği şart — GCC 10+, Clang 12+, MSVC 19.28+.

⚠️ Dikkat: SFINAE ve Concepts birlikte kullanılabilir ama aynı overload'da ikisini karıştırmak kafa karıştırır. Bir fonksiyon grubu için ya SFINAE ya Concepts kullan — karma yaklaşımdan kaçın.


Yaygın Hatalar

1. Concept Constraint Sırası

// YANLIS: daha spesifik olan altta kalırsa belirsizlik olabilir
template <typename T>
void isle(T) { std::cout << "genel" << std::endl; }

template <std::integral T>
void isle(T) { std::cout << "integral" << std::endl; }

// DOGRU: Concepts subsumption — daha spesifik concept kazanır
template <std::integral T>
void isle2(T) { std::cout << "integral" << std::endl; }

template <std::signed_integral T>  // signed_integral, integral'ı subsume eder
void isle2(T) { std::cout << "signed integral" << std::endl; }

// isle2(42) → "signed integral" (daha spesifik)
// isle2(42u) → "integral" (unsigned, signed_integral değil)

Concepts subsumption kuralını destekler. Bir concept diğerini "kapsıyorsa" (subsume ediyorsa), daha spesifik olan tercih edilir. Bu, SFINAE'de olmayan ve belirsizlik sorunlarını çözen güçlü bir mekanizmadır.

2. requires requires Karışıklığı

// İlk requires: clause (constraint)
// İkinci requires: expression (sorgulama)
template <typename T>
requires requires(T a, T b) { a + b; }  // requires requires
void topla(T a, T b) { /* ... */ }

// Daha temiz: concept tanımla
template <typename T>
concept Addable = requires(T a, T b) { a + b; };

template <Addable T>  // Tek requires, daha okunabilir
void topla2(T a, T b) { /* ... */ }

requires requires doğru sözdizimi ama tekrarlı göründüğü için kafa karıştırır. Mümkünse bir concept tanımla ve onu kullan.


Özet

  • Type traits (<type_traits>) derleme zamanında tip özelliklerini sorgular — is_integral, is_same, remove_const gibi araçlarla tipleri tanır ve dönüştürürsün.

  • SFINAE template substitution hatalarını "hata" olarak değil "bu aday uygun değil" olarak yorumlar — bu sayede aynı isimli fonksiyonların farklı tipler için farklı versiyonları yazılabilir.

  • `static_assert` derleme zamanı kontrolü yapar ve başarısız olduğunda anlaşılır hata mesajı verir — template sınıflarında erken uyarı mekanizmasıdır.

  • C++20 Concepts, SFINAE'nin yaptığı her şeyi çok daha temiz, okunabilir ve bakımı kolay bir sözdizimi ile yapar — template <std::integral T> yazmak, enable_if zincirine göre çığır açıcıdır.

  • `requires` clause ve expression ile karmaşık tip gereksinimleri tanımlanır — "bu tipin .size() metodu var mı ve size_t döndürüyor mu?" gibi sorular tek satırda ifade edilir.

  • Migration stratejisi: Yeni kodda Concepts kullan, mevcut SFINAE'yi çalışıyorsa koru, refactor ederken kademeli geçiş yap.