Concepts (C++20)
Template'ler C++'ın en güçlü araçlarından biri. Ama bir sorunları var: yanlış tip verdiğinde derleyici hata mesajları korkunç uzunlukta ve anlaşılmaz oluyor. Bir vector<int>'e sort uygulamak istiyorsun ama yanlışlıkla < operatörü olmayan bir tip verdin — derleyici sana sayfalarca hata mesajı gösteriyor ve gerçek sorunun ne olduğunu bulmak bir dedektiflik macerası.
Concepts, bu sorunu çözer. Template parametrelerine kısıtlamalar (constraints) tanımlarsın: "Bu template sadece tamsayı tiplerle çalışır" veya "Bu tip karşılaştırılabilir olmalı" gibi. Yanlış tip verdiğinde, derleyici kısa ve anlaşılır bir mesajla seni uyarır.
Bir iş ilanı analojisi düşün: "Yazılım geliştirici arıyoruz — en az 3 yıl C++ deneyimi, template bilgisi zorunlu." Bu ilan bir concept'tir — adayın (tipin) karşılaması gereken şartları belirtir. Şartları karşılamayan aday başvuramaz, başvursa da net bir ret mesajı alır.
Concepts Öncesi — Sorun
Template'lerde tip kısıtlaması olmadığında neler olur, bir bakalım:
#include <iostream>
template<typename T>
T add(T a, T b) {
return a + b; // T'nin + operatörü olması gerekiyor
}
int main() {
std::cout << add(3, 5) << "\n"; // OK — int + int
std::cout << add(1.5, 2.5) << "\n"; // OK — double + double
// add(std::vector<int>{}, std::vector<int>{});
// Eğer bunu yazsaydın: uzun ve karışık hata mesajı...
return 0;
}Bu basit örnekte sorun yok. Ama büyük template kütüphanelerinde (STL dahil) hata mesajları yüzlerce satır olabilir. Concepts bu durumu düzeltir.
Concept Nedir?
Concept, bir tipin karşılaması gereken şartları tanımlayan bir derleme zamanı ifadesidir. Boolean bir değer döner: tip şartları karşılıyorsa true, karşılamıyorsa false.
requires Clause
requires anahtar kelimesi, template parametresine kısıtlama ekler:
#include <concepts>
#include <iostream>
// Sadece tamsayı tipleri kabul et
template<typename T>
requires std::integral<T>
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(3, 5) << "\n"; // OK — int integral
std::cout << add(10L, 20L) << "\n"; // OK — long integral
// add(1.5, 2.5); // HATA! double integral değil
// Hata mesajı: "constraints not satisfied" — kısa ve anlaşılır
return 0;
}Concept Kullanımının Dört Yolu
Aynı kısıtlamayı farklı söz dizimleriyle yazabilirsin:
#include <concepts>
#include <iostream>
// Yol 1: requires clause (en açık)
template<typename T>
requires std::integral<T>
T add1(T a, T b) { return a + b; }
// Yol 2: Concept doğrudan template parametresinde
template<std::integral T>
T add2(T a, T b) { return a + b; }
// Yol 3: Trailing requires
template<typename T>
T add3(T a, T b) requires std::integral<T> { return a + b; }
// Yol 4: Abbreviated template (auto ile)
auto add4(std::integral auto a, std::integral auto b) {
return a + b;
}
int main() {
std::cout << add1(3, 5) << "\n";
std::cout << add2(3, 5) << "\n";
std::cout << add3(3, 5) << "\n";
std::cout << add4(3, 5) << "\n";
return 0;
}💡 Hangi söz dizimini kullanmalı? Basit kısıtlamalar için Yol 2 (
template<std::integral T>) en okunabilir. Karmaşık kısıtlamalar için Yol 1 (requires ...) daha uygun. Yol 4 (abbreviated) kısa fonksiyonlar için pratik.
Built-in Concepts
C++20, <concepts> header'ında birçok hazır concept sunar. En sık kullanılanlar:
Temel Tip Concepts
#include <concepts>
#include <string>
#include <iostream>
// std::integral — tamsayı tipleri (int, long, short, char, bool...)
template<std::integral T>
void printInt(T x) {
std::cout << "Tamsayi: " << x << "\n";
}
// std::floating_point — ondalıklı tipleri (float, double, long double)
template<std::floating_point T>
void printFloat(T x) {
std::cout << "Ondalikli: " << x << "\n";
}
// std::same_as — belirli bir tip mi?
template<std::same_as<std::string> T>
void printString(T s) {
std::cout << "String: " << s << "\n";
}
int main() {
printInt(42);
printInt('A'); // char da integral
// printInt(3.14); // HATA!
printFloat(3.14);
printFloat(2.71f);
// printFloat(42); // HATA!
printString(std::string("Merhaba"));
// printString(42); // HATA!
return 0;
}Karşılaştırma ve Sıralama Concepts
#include <concepts>
#include <vector>
#include <algorithm>
#include <iostream>
// std::totally_ordered — <, >, <=, >= destekleyen tipler
template<std::totally_ordered T>
T findMax(const std::vector<T>& data) {
return *std::max_element(data.begin(), data.end());
}
// std::equality_comparable — == ve != destekleyen tipler
template<std::equality_comparable T>
bool contains(const std::vector<T>& data, const T& value) {
return std::find(data.begin(), data.end(), value) != data.end();
}
int main() {
std::vector<int> nums = {5, 3, 8, 1, 9};
std::cout << "Max: " << findMax(nums) << "\n";
std::cout << "9 var mi? " << contains(nums, 9) << "\n";
return 0;
}Sık Kullanılan Built-in Concepts
| Concept | Anlamı |
|---|---|
std::integral | Tamsayı tipleri |
std::floating_point | Ondalıklı sayı tipleri |
std::same_as<T> | Tam olarak T tipi |
std::convertible_to<T> | T'ye dönüştürülebilir |
std::derived_from<Base> | Base sınıfından türemiş |
std::totally_ordered | Karşılaştırma operatörleri var |
std::equality_comparable | == ve != var |
std::copyable | Kopyalanabilir |
std::movable | Taşınabilir |
std::default_initializable | Varsayılan constructor var |
std::invocable<Args...> | Çağrılabilir (callable) |
Custom Concept Tanımlama
Kendi concept'lerini tanımlayabilirsin. concept anahtar kelimesi ile bir boolean ifade tanımlarsın:
Basit Custom Concept
#include <concepts>
#include <string>
#include <iostream>
// Sayısal tip — integral veya floating point
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template<Numeric T>
T multiply(T a, T b) {
return a * b;
}
int main() {
std::cout << multiply(3, 5) << "\n"; // 15
std::cout << multiply(2.5, 4.0) << "\n"; // 10
// multiply(std::string("a"), std::string("b")); // HATA!
return 0;
}requires Expression ile
requires ifadesi, bir tipin belirli operasyonları destekleyip desteklemediğini kontrol eder:
#include <iostream>
#include <string>
#include <vector>
#include <concepts>
// T tipinin .size() metodu olmalı ve bu size_t döndürmeli
template<typename T>
concept HasSize = requires(T t) {
{ t.size() } -> std::convertible_to<std::size_t>;
};
// T tipinin operator<< ile yazdırılabilir olması
template<typename T>
concept Printable = requires(std::ostream& os, T t) {
{ os << t } -> std::same_as<std::ostream&>;
};
template<HasSize T>
void printSize(const T& container) {
std::cout << "Size: " << container.size() << "\n";
}
template<Printable T>
void display(const T& value) {
std::cout << "Deger: " << value << "\n";
}
int main() {
std::vector<int> v = {1, 2, 3};
std::string s = "Merhaba";
printSize(v); // Size: 3
printSize(s); // Size: 7
display(42);
display("Hello");
// printSize(42); // HATA! int'in size() metodu yok
return 0;
}Birden Fazla Kısıtlama
#include <concepts>
#include <iostream>
// Karmaşık concept — birden fazla şart
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>; // + operatörü olmalı, aynı tipi döndürmeli
{ a - b } -> std::same_as<T>; // - operatörü olmalı
};
template<typename T>
concept PrintableNumber = Addable<T> && requires(std::ostream& os, T t) {
{ os << t } -> std::same_as<std::ostream&>;
};
template<PrintableNumber T>
void calculate(T a, T b) {
std::cout << "Toplam: " << (a + b) << "\n";
std::cout << "Fark: " << (a - b) << "\n";
}
int main() {
calculate(10, 3); // OK
calculate(2.5, 1.5); // OK
return 0;
}Concept ile Template Constraint
Concepts, template sınıflarını da kısıtlayabilir:
#include <concepts>
#include <iostream>
#include <vector>
#include <numeric>
template<typename T>
concept Arithmetic = std::integral<T> || std::floating_point<T>;
template<Arithmetic T>
class Statistics {
std::vector<T> data;
public:
void add(T value) {
data.push_back(value);
}
double mean() const {
if (data.empty()) return 0;
return static_cast<double>(
std::accumulate(data.begin(), data.end(), T{})
) / data.size();
}
T min() const {
return *std::min_element(data.begin(), data.end());
}
T max() const {
return *std::max_element(data.begin(), data.end());
}
};
int main() {
Statistics<int> intStats;
intStats.add(10);
intStats.add(20);
intStats.add(30);
std::cout << "Ortalama: " << intStats.mean() << "\n";
std::cout << "Min: " << intStats.min() << "\n";
std::cout << "Max: " << intStats.max() << "\n";
Statistics<double> dblStats;
dblStats.add(1.5);
dblStats.add(2.7);
dblStats.add(3.9);
std::cout << "Ortalama: " << dblStats.mean() << "\n";
// Statistics<std::string> strStats; // HATA! string Arithmetic değil
return 0;
}Concept ile Overloading
Aynı isimle farklı kısıtlamalara sahip fonksiyonlar tanımlayabilirsin — en spesifik olan seçilir:
#include <concepts>
#include <iostream>
#include <string>
// Genel versiyon
template<typename T>
void process(T value) {
std::cout << "Genel: " << value << "\n";
}
// Tamsayılar için özel versiyon
template<std::integral T>
void process(T value) {
std::cout << "Tamsayi: " << value << " (hex: 0x"
<< std::hex << value << std::dec << ")\n";
}
// Ondalıklı sayılar için özel versiyon
template<std::floating_point T>
void process(T value) {
std::cout << "Ondalikli: " << std::fixed << value << "\n";
}
int main() {
process(42); // Tamsayi: 42 (hex: 0x2a)
process(3.14); // Ondalikli: 3.140000
process("Merhaba"); // Genel: Merhaba
return 0;
}Concepts vs SFINAE Karşılaştırma
C++20 öncesinde template kısıtlamaları SFINAE (Substitution Failure Is Not An Error) ile yapılıyordu. SFINAE, std::enable_if ve karmaşık template metaprogramlama gerektiriyordu. Concepts bunu dramatik şekilde basitleştirdi.
SFINAE Yolu (Eski — Karmaşık)
#include <type_traits>
#include <iostream>
// SFINAE ile — okuması zor, yazması zor
template<typename T,
typename = std::enable_if_t<std::is_integral_v<T>>>
T addOld(T a, T b) {
return a + b;
}
// İki kısıtlama — daha da karmaşık
template<typename T,
std::enable_if_t<std::is_integral_v<T> ||
std::is_floating_point_v<T>, int> = 0>
T multiplyOld(T a, T b) {
return a * b;
}
int main() {
std::cout << addOld(3, 5) << "\n";
std::cout << multiplyOld(2.5, 4.0) << "\n";
return 0;
}Concepts Yolu (Yeni — Temiz)
#include <concepts>
#include <iostream>
// Concepts ile — okunabilir, yazılabilir
template<std::integral T>
T addNew(T a, T b) {
return a + b;
}
template<typename T>
requires std::integral<T> || std::floating_point<T>
T multiplyNew(T a, T b) {
return a * b;
}
int main() {
std::cout << addNew(3, 5) << "\n";
std::cout << multiplyNew(2.5, 4.0) << "\n";
return 0;
}Karşılaştırma Tablosu
| Özellik | SFINAE | Concepts | ||
|---|---|---|---|---|
| Okunabilirlik | ❌ Çok düşük | ✅ Çok yüksek | ||
| Hata mesajları | ❌ Uzun ve karışık | ✅ Kısa ve anlaşılır | ||
| Öğrenme eğrisi | ❌ Dik | ✅ Düşük | ||
| İfade gücü | ✅ Her şeyi yapabilir | ✅ Her şeyi yapabilir | ||
| Derleyici desteği | ✅ C++11+ | ⚡ C++20+ gerekli | ||
| Composability | ❌ Zor | ✅ &&, ` | ` ile kolay |
⚠️ Concepts, C++20 gerektirir. Derleme sırasında
-std=c++20flag'ını kullanmayı unutma. Eski projeler veya eski derleyiciler concepts desteklemeyebilir — bu durumda SFINAE veyastatic_assertkullan.
Pratik Örnek: Generic Container Printer
#include <concepts>
#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <map>
#include <string>
// Container concept — begin(), end(), size() olmalı
template<typename T>
concept Container = requires(T t) {
t.begin();
t.end();
t.size();
};
// Key-value container concept
template<typename T>
concept KeyValueContainer = Container<T> && requires(T t) {
typename T::key_type;
typename T::mapped_type;
};
// Normal container'ları yazdır
template<Container T>
void print(const T& c) requires (!KeyValueContainer<T>) {
std::cout << "[";
bool first = true;
for (const auto& item : c) {
if (!first) std::cout << ", ";
std::cout << item;
first = false;
}
std::cout << "] (size: " << c.size() << ")\n";
}
// Key-value container'ları yazdır
template<KeyValueContainer T>
void print(const T& c) {
std::cout << "{";
bool first = true;
for (const auto& [key, value] : c) {
if (!first) std::cout << ", ";
std::cout << key << ": " << value;
first = false;
}
std::cout << "} (size: " << c.size() << ")\n";
}
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
std::list<std::string> l = {"Ali", "Ayse"};
std::set<int> s = {9, 3, 7, 1, 5};
std::map<std::string, int> m = {{"x", 1}, {"y", 2}};
print(v); // [1, 2, 3, 4, 5] (size: 5)
print(l); // [Ali, Ayse] (size: 2)
print(s); // [1, 3, 5, 7, 9] (size: 5)
print(m); // {x: 1, y: 2} (size: 2)
// print(42); // HATA! int bir Container değil
return 0;
}Özet
Concepts, template parametrelerine derleme zamanı kısıtlamalar tanımlayan C++20 özelliğidir. Hata mesajlarını kısa ve anlaşılır hale getirir.
`requires` clause ile kısıtlama eklersin. Dört farklı söz dizimi var — en okunabilir olan
template<std::integral T>biçimidir.Built-in concepts (
std::integral,std::floating_point,std::same_as,std::totally_orderedvb.) çoğu ihtiyacı karşılar.Custom concept tanımlamak için
conceptanahtar kelimesi verequiresifadesi kullanılır. Bir tipin belirli operasyonları destekleyip desteklemediğini kontrol edebilirsin.Concepts, template sınıflarını kısıtlamak ve aynı isimle farklı kısıtlamalara sahip overload'lar tanımlamak için kullanılır.
SFINAE'ye göre çok daha okunabilir, öğrenmesi kolay ve hata mesajları net. Yeni projeler için concepts tercih et, eski projeler SFINAE kullanmaya devam edebilir.
AI Asistan
Sorularını yanıtlamaya hazır