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
| Trait | Ne Sorar | Örnek (true) |
|---|---|---|
is_integral | Tam sayı mı? | int, char, bool, long |
is_floating_point | Ondalıklı sayı mı? | float, double |
is_arithmetic | Sayı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_constructible | Kopyalanabilir mi? | unique_ptr → false |
is_trivially_copyable | memcpy ile kopyalanabilir mi? | POD tipler |
is_void | void mı? | is_void_v<void> |
is_reference | Referans 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:
Okunabilirlik:
enable_if_t<is_integral_v<T> && !is_same_v<T, bool>, T>gibi ifadeler göz yakarHata mesajları: Koşul sağlanmadığında compiler sayfalarca template hata mesajı verir
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ür | Sözdizimi | Anlamı |
|---|---|---|
| Simple | t.foo(); | İfade geçerli mi? |
| Type | typename T::value_type; | Tip var mı? |
| Compound | { t.foo() } -> concept; | İfade geçerli mi VE sonuç tipi constraint'i sağlıyor mu? |
| Nested | requires 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
| Concept | Başlık | Ne 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
| SFINAE | Concepts 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
Yeni kodda Concepts kullan. Hiçbir neden yokken SFINAE yazma.
Mevcut SFINAE'yi hemen değiştirme. Çalışan koda dokunma — "if it ain't broke, don't fix it."
Kademeli geçiş yap. Yeni overload eklediğinde veya refactor yaparken Concepts'e geçir.
static_assert'ları koru. Concepts overload seçimi yapar ama static_assert özel hata mesajı verir — ikisi farklı amaçlara hizmet eder.
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_constgibi 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_ifzincirine 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ı vesize_tdö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.
AI Asistan
Sorularını yanıtlamaya hazır