Template Specialization
Template'ler harika — bir kere yaz, her tip için çalışsın. Ama ya belirli bir tip için farklı davranış gerekiyorsa? Örneğin genel template'in int, double, float için iyi çalışıyor ama const char* veya bool için mantıklı sonuç vermiyor.
İşte template specialization tam olarak bunu çözer: genel kuralın istisnasını tanımlamak.
Neden Specialization Gerekli?
Bir karşılaştırma fonksiyonu düşün:
template<typename T>
bool isEqual(T a, T b) {
return a == b;
}
int main() {
std::cout << std::boolalpha;
std::cout << isEqual(3, 3) << "\n"; // true ✅
std::cout << isEqual(3.14, 3.14) << "\n"; // true ✅
const char* s1 = "hello";
const char* s2 = "hello";
std::cout << isEqual(s1, s2) << "\n"; // ??? Muhtemelen false!
}Son çağrı büyük ihtimalle false döner. Çünkü const char* için == operatörü pointer adreslerini karşılaştırır, string içeriklerini değil. İki farklı bellekteki "hello" stringi farklı adreslerde olabilir.
Genel template'in const char* için yanlış çalışıyor. Bu tipe özel bir versiyon lazım.
Analoji: Postane. Genel kural: "Zarfları tartıp pul yapıştır." Ama taahhütlü mektup için farklı bir prosedür var — tartma değil, kayıt numarası ver, imza al. Specialization, genel kuralın istisnasını tanımlamak gibidir.
Full (Explicit) Specialization
Full specialization, belirli bir tip için template'in tamamen farklı bir versiyonunu yazmaktır:
// Genel template
template<typename T>
bool isEqual(T a, T b) {
return a == b;
}
// const char* için full specialization
template<>
bool isEqual<const char*>(const char* a, const char* b) {
return std::strcmp(a, b) == 0;
}
int main() {
std::cout << std::boolalpha;
std::cout << isEqual(3, 3) << "\n"; // true (genel)
std::cout << isEqual("hello", "hello") << "\n"; // true (specialization)
}Söz dizimi:
template<>— "Bu bir specialization" (açılı ayraçlar boş)isEqual<const char*>— "const char* için özelleştirilmiş"
Derleyici, isEqual("hello", "hello") gördüğünde const char* specialization'ını kullanır. Diğer tipler için genel template'i kullanır.
Class Template için Full Specialization
Sınıf template'lerinde de aynı mantık geçerli:
// Genel template
template<typename T>
class Printer {
public:
void print(const T& value) {
std::cout << value << "\n";
}
};
// bool için full specialization
template<>
class Printer<bool> {
public:
void print(const bool& value) {
std::cout << (value ? "true" : "false") << "\n";
}
};
int main() {
Printer<int> ip;
ip.print(42); // 42
Printer<bool> bp;
bp.print(true); // true (specialization)
}Full specialization'da sınıfı tamamen yeniden yazıyorsun. Genel template'in hiçbir üyesi otomatik olarak gelmez. Her şeyi baştan tanımlamalısın.
⚠️ Dikkat: Full specialization yapınca genel template'in hiçbir fonksiyonu miras alınmaz. Tüm fonksiyonları yeniden yazman gerekir. Bu, büyük sınıflarda sıkıntılı olabilir.
Partial Specialization (Class Templates)
Partial specialization, tüm parametreleri değil bazılarını sabitler. Sadece class template'lerde çalışır — fonksiyon template'lerinde desteklenmez.
// Genel template — iki parametre
template<typename T, typename U>
class Pair {
public:
void describe() {
std::cout << "Generic Pair<T, U>\n";
}
};
// Partial specialization — her ikisi aynı tip olduğunda
template<typename T>
class Pair<T, T> {
public:
void describe() {
std::cout << "Same-type Pair<T, T>\n";
}
};
// Partial specialization — ikinci parametre int olduğunda
template<typename T>
class Pair<T, int> {
public:
void describe() {
std::cout << "Pair<T, int>\n";
}
};
int main() {
Pair<double, std::string> p1;
p1.describe(); // Generic Pair<T, U>
Pair<int, int> p2;
p2.describe(); // Same-type Pair<T, T>
Pair<std::string, int> p3;
p3.describe(); // Pair<T, int>
}Pointer Türleri için Partial Specialization
Yaygın bir kullanım: pointer tipleri için özel davranış:
// Genel template
template<typename T>
class Storage {
T value;
public:
Storage(const T& v) : value(v) {}
void print() const {
std::cout << "Value: " << value << "\n";
}
~Storage() = default;
};
// Pointer tipleri için partial specialization
template<typename T>
class Storage<T*> {
T* ptr;
public:
Storage(T* p) : ptr(p) {}
void print() const {
if (ptr) {
std::cout << "Pointer to: " << *ptr << "\n";
} else {
std::cout << "Null pointer\n";
}
}
~Storage() {
delete ptr; // Pointer'ın sahipliğini al
}
};
int main() {
Storage<int> s1(42);
s1.print(); // Value: 42
Storage<int*> s2(new int(99));
s2.print(); // Pointer to: 99
// s2 yıkılınca new int(99) otomatik delete edilir
}Storage<int*> yazdığında, T = int, T* = int* olur ve pointer specialization'ı kullanılır. Bu sayede pointer tipleri için bellek yönetimi ekleyebildik.
Gerçek Dünya Örneği: String Özel Davranışı
Standart kütüphanenin std::hash yapısı, farklı tipler için specialization kullanır:
#include <iostream>
#include <string>
#include <functional>
template<typename T>
struct SimpleHash {
size_t operator()(const T& value) const {
// Genel hash — basit byte tabanlı
return std::hash<T>{}(value);
}
std::string describe() const {
return "Generic hash";
}
};
// std::string için specialization
template<>
struct SimpleHash<std::string> {
size_t operator()(const std::string& value) const {
// DJB2 hash algoritması
size_t hash = 5381;
for (char c : value) {
hash = ((hash << 5) + hash) + c;
}
return hash;
}
std::string describe() const {
return "DJB2 string hash";
}
};
int main() {
SimpleHash<int> intHash;
SimpleHash<std::string> strHash;
std::cout << intHash.describe() << ": "
<< intHash(42) << "\n";
std::cout << strHash.describe() << ": "
<< strHash("hello") << "\n";
}String'ler için özel bir hash algoritması kullanıyoruz çünkü genel byte tabanlı hash string'ler için iyi çalışmaz.
Specialization Seçim Kuralları
Birden fazla aday varsa derleyici en spesifik olanı seçer:
1. Full specialization (tam eşleşme)
2. Partial specialization (kısmi eşleşme)
3. Primary template (genel şablon)template<typename T>
struct Info {
static std::string name() { return "General"; }
};
template<typename T>
struct Info<T*> {
static std::string name() { return "Pointer"; }
};
template<>
struct Info<int*> {
static std::string name() { return "Int pointer"; }
};
int main() {
std::cout << Info<double>::name() << "\n"; // General
std::cout << Info<double*>::name() << "\n"; // Pointer
std::cout << Info<int*>::name() << "\n"; // Int pointer (en spesifik)
}int* için hem Info<T*> (partial) hem Info<int*> (full) geçerli. Derleyici en spesifik olanı seçer: full specialization.
💡 Kural: Derleyici her zaman "en dar eşleşmeyi" tercih eder. Full > Partial > Primary.
Fonksiyon Template'lerinde Specialization vs Overloading
Fonksiyon template'lerinde partial specialization yapılamaz. Bunun yerine overloading kullanılır:
// Genel template
template<typename T>
void process(T value) {
std::cout << "Generic: " << value << "\n";
}
// Overload (specialization DEĞİL, overload!)
void process(const char* str) {
std::cout << "C-string: " << str << "\n";
}
// Full specialization (mümkün ama önerilmez)
template<>
void process<int>(int value) {
std::cout << "Int: " << value << "\n";
}
int main() {
process(3.14); // Generic: 3.14
process("hello"); // C-string: hello (overload)
process(42); // Int: 42 (specialization)
}⚠️ Pratik tavsiye: Fonksiyon template'lerinde specialization yerine overloading tercih et. Overloading, overload resolution kuralları açısından daha öngörülebilir çalışır. Specialization beklenmedik sürprizler yapabilir.
SFINAE — Kısa Bir Giriş
SFINAE (Substitution Failure Is Not An Error) C++ template sisteminin en garip ama en güçlü özelliklerinden biridir.
Basitçe: derleyici bir template'i instantiate etmeye çalışırken tip uyumsuzluğu yaşarsa, hata vermez. O template adayını sessizce listeden çıkarır ve diğer adaylara bakar.
#include <type_traits>
// Sadece integral tipler için çalışan fonksiyon
template<typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
doubleValue(T x) {
return x * 2;
}
// Sadece floating-point tipler için çalışan fonksiyon
template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
doubleValue(T x) {
return x * 2.0;
}
int main() {
std::cout << doubleValue(5) << "\n"; // 10 (integral versiyon)
std::cout << doubleValue(3.14) << "\n"; // 6.28 (floating versiyon)
// doubleValue("hello"); // Hata — iki versiyon da başarısız
}doubleValue(5) çağrıldığında:
Derleyici integral versiyonu dener →
is_integral<int>→ true → ✅ Bu çalışırFloating versiyonu dener →
is_floating_point<int>→ false → substitution failure → sessizce atla
Bu bir hata değil, SFINAE kuralı.
C++20 ile Daha Temiz: Concepts
SFINAE güçlü ama okuması zor. C++20'de Concepts ile aynı şey çok daha temiz yazılır:
#include <concepts>
template<std::integral T>
T doubleValue(T x) {
return x * 2;
}
template<std::floating_point T>
T doubleValue(T x) {
return x * 2.0;
}Aynı sonuç, çok daha okunabilir. SFINAE'nin *varlığını* bil ama yeni kod yazıyorsan C++20 Concepts tercih et.
💡 Not: SFINAE, ileri seviye bir konu. Şu an için "derleyici tip uyumsuzluğunda hata vermek yerine alternatif arar" bilgisi yeterli. Kütüphane yazarı olduğunda derinlemesine öğrenirsin.
SFINAE'nin Yaygın Kullanım Yerleri
Standart kütüphanede SFINAE her yerde:
std::enable_if— koşullu fonksiyon tanımlamastd::is_integral,std::is_floating_point— tip kontrolleristd::is_constructible— constructor varlığı kontrolüIterator trait'leri — input/output/random access ayrımı
Bu araçlar kütüphane yazarları için kritiktir. Uygulama geliştiricisi olarak SFINAE'yi doğrudan yazman nadiren gerekir — ama kullandığın kütüphanelerin hata mesajlarını anlamak için kavramı bilmek gerekir.
Pratik Örnek: Serializer
Specialization'ın gerçek bir projede nasıl kullanılacağını tam bir örnekle görelim:
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
// Primary template — genel durum
template<typename T>
struct Serializer {
static std::string toJson(const T& value) {
std::ostringstream oss;
oss << value;
return oss.str();
}
static T fromJson(const std::string& json) {
std::istringstream iss(json);
T value;
iss >> value;
return value;
}
};
// bool specialization — "true"/"false" yazdır, 1/0 değil
template<>
struct Serializer<bool> {
static std::string toJson(const bool& value) {
return value ? "true" : "false";
}
static bool fromJson(const std::string& json) {
return json == "true";
}
};
// string specialization — tırnak ekle
template<>
struct Serializer<std::string> {
static std::string toJson(const std::string& value) {
return "\"" + value + "\"";
}
static std::string fromJson(const std::string& json) {
if (json.size() >= 2 && json.front() == '"' && json.back() == '"') {
return json.substr(1, json.size() - 2);
}
return json;
}
};
// vector partial specialization
template<typename T>
struct Serializer<std::vector<T>> {
static std::string toJson(const std::vector<T>& vec) {
std::string result = "[";
for (size_t i = 0; i < vec.size(); i++) {
if (i > 0) result += ", ";
result += Serializer<T>::toJson(vec[i]);
}
result += "]";
return result;
}
};
int main() {
std::cout << Serializer<int>::toJson(42) << "\n"; // 42
std::cout << Serializer<bool>::toJson(true) << "\n"; // true
std::cout << Serializer<std::string>::toJson("hello") << "\n"; // "hello"
std::vector<int> nums = {1, 2, 3};
std::cout << Serializer<std::vector<int>>::toJson(nums) << "\n"; // [1, 2, 3]
std::vector<std::string> words = {"a", "b", "c"};
std::cout << Serializer<std::vector<std::string>>::toJson(words) << "\n";
// ["a", "b", "c"]
// Deserialization
int num = Serializer<int>::fromJson("42");
bool flag = Serializer<bool>::fromJson("true");
std::cout << num << ", " << std::boolalpha << flag << "\n"; // 42, true
}Bu örnekte:
Primary template: Genel tipleri
<<operatörü ile serialize ederbool specialization: "true"/"false" kullanır (1/0 yerine)
string specialization: Tırnak işaretleri ekler
vector partial specialization: Elemanları
[]içinde sıralır, her elemanı kendi serializer'ı ile işler
Pratik Örnek: TypeName Utility
Debugging sırasında bir tipin adını yazdırmak isteyebilirsin:
#include <iostream>
#include <string>
#include <vector>
// Genel template — bilinmeyen tip
template<typename T>
struct TypeName {
static std::string get() { return "unknown"; }
};
// Specialization'lar
template<> struct TypeName<int> {
static std::string get() { return "int"; }
};
template<> struct TypeName<double> {
static std::string get() { return "double"; }
};
template<> struct TypeName<std::string> {
static std::string get() { return "std::string"; }
};
template<> struct TypeName<bool> {
static std::string get() { return "bool"; }
};
// Partial specialization — vector<T>
template<typename T>
struct TypeName<std::vector<T>> {
static std::string get() {
return "std::vector<" + TypeName<T>::get() + ">";
}
};
// Helper fonksiyon
template<typename T>
void printType(const T&) {
std::cout << "Type: " << TypeName<T>::get() << "\n";
}
int main() {
int x = 42;
double d = 3.14;
std::string s = "hello";
std::vector<int> v = {1, 2, 3};
std::vector<std::string> vs = {"a", "b"};
printType(x); // Type: int
printType(d); // Type: double
printType(s); // Type: std::string
printType(v); // Type: std::vector<int>
printType(vs); // Type: std::vector<std::string>
}Bu örnek hem full specialization'ı (int, double, string, bool) hem de partial specialization'ı (vector<T>) gösteriyor.
Standart Kütüphanede Specialization Örnekleri
C++ standart kütüphanesi specialization'ı yoğun kullanır. Bunları bilmek, specialization'ın neden var olduğunu daha iyi anlamana yardımcı olur:
std::vector<bool>
std::vector<bool> notorious bir full specialization'dır. Normal vector her eleman için ayrı bellek alanı kullanır, ama bool versiyonu bit packing yapar — her bool sadece 1 bit yer kaplar:
std::vector<int> vi(1000); // ~4000 byte
std::vector<bool> vb(1000); // ~125 byte (1000 bit)Bu optimizasyon mantıklı görünse de pratikte birçok sorun yaratır (bool& referans dönemez, proxy nesneleri kullanır). C++ topluluğunun çoğu bunu bir tasarım hatası olarak görür.
std::hash
std::hash farklı tipler için specialization kullanır:
// Kendi tipinin hash'ini tanımla
struct Point {
int x, y;
};
template<>
struct std::hash<Point> {
size_t operator()(const Point& p) const {
return std::hash<int>{}(p.x) ^ (std::hash<int>{}(p.y) << 1);
}
};
// Artık unordered_map/set ile kullanılabilir
std::unordered_set<Point> points;std::char_traits
std::basic_string farklı karakter tipleri için std::char_traits specialization'larını kullanır:
char_traits<char>— ASCII/UTF-8char_traits<wchar_t>— wide characterchar_traits<char16_t>— UTF-16char_traits<char32_t>— UTF-32
if constexpr: Modern Alternatif
C++17'den itibaren bazı specialization senaryoları if constexpr ile daha basit çözülebilir:
// Specialization ile
template<typename T>
struct Serializer {
static std::string serialize(const T& val) {
return std::to_string(val);
}
};
template<>
struct Serializer<std::string> {
static std::string serialize(const std::string& val) {
return "\"" + val + "\"";
}
};
template<>
struct Serializer<bool> {
static std::string serialize(bool val) {
return val ? "true" : "false";
}
};// if constexpr ile — tek fonksiyon
template<typename T>
std::string serialize(const T& val) {
if constexpr (std::is_same_v<T, std::string>) {
return "\"" + val + "\"";
} else if constexpr (std::is_same_v<T, bool>) {
return val ? "true" : "false";
} else if constexpr (std::is_arithmetic_v<T>) {
return std::to_string(val);
} else {
static_assert(false, "Unsupported type");
}
}
int main() {
std::cout << serialize(42) << "\n"; // 42
std::cout << serialize(true) << "\n"; // true
std::cout << serialize(std::string("hi")) << "\n"; // "hi"
}if constexpr daha az boilerplate kod gerektirir. Ama tip sayısı arttıkça veya her tipin tamamen farklı implementasyonu varsa, specialization hâlâ daha temiz olabilir.
Specialization Best Practices
Önce overloading dene (fonksiyon template'lerinde). Specialization son çare olsun.
Primary template'i iyi tasarla. Çoğu tip için doğru çalışan bir genel versiyon yaz.
Specialization sayısını az tut. 10 tane specialization varsa, belki template tasarımı yanlış.
Document et. Neden specialization gerektiğini açıklayan bir yorum ekle.
Test et. Hem primary hem specialized versiyonları ayrı ayrı test et.
// İyi practice: Neden specialized olduğunu açıkla
template<>
struct Hasher<const char*> {
// const char* için == operator'ı pointer karşılaştırır,
// içerik karşılaştırması için strcmp gerekli.
// Bu yüzden özel bir hash implementasyonu kullanıyoruz.
size_t operator()(const char* str) const {
size_t hash = 0;
while (*str) hash = hash * 31 + *str++;
return hash;
}
};Özet
Full specialization (
template<>), belirli bir tip için template'in tamamen farklı bir versiyonunu tanımlar.Partial specialization, tip parametrelerinin bir kısmını sabitler veya kısıtlar (pointer, referans gibi). Sadece class template'lerde çalışır.
Specialization genellikle string, pointer, bool gibi tipler için özel davranış gerektiğinde kullanılır — genel template'in o tip için doğru çalışmadığı durumlarda.
SFINAE (Substitution Failure Is Not An Error), derleyicinin tip uyumsuzluğunda hata vermek yerine o adayı sessizce atlaması kuralıdır. C++20 Concepts ile daha temiz yazılır.
Derleyici seçim sırası: Full specialization > Partial specialization > Primary template — her zaman en spesifik eşleşme tercih edilir.
C++17
if constexprbazı senaryolarda specialization'a basit bir alternatif sunar. Fonksiyon template'lerinde specialization yerine overloading tercih et.
AI Asistan
Sorularını yanıtlamaya hazır