C++20 Ranges Library Derinlemesine
Bir fabrika montaj hattı düşün. Hammadde bir ucundan girer, her istasyonda bir işlem yapılır — kesilir, boyanır, kontrol edilir, paketlenir — ve ürün diğer ucundan çıkar. Ama bu hat akıllıdır: son istasyon bir ürün istemeden önceki istasyonlar çalışmaz. Kimse gereksiz yere kesim yapmaz, kimse boşa boya harcamaz. Bu lazy evaluation'dır ve C++20 Ranges Library'nin kalbinde tam olarak bu mantık yatar.
C++98'den beri <algorithm> ile std::sort, std::find, std::transform gibi fonksiyonlar kullanıyoruz. Ama hepsine iki iterator geçmek gerekiyor: begin ve end. Birden fazla işlemi zincirlmek istediğinde her adım için geçici container oluşturmak zorundasın. Ranges Library bu sorunu kökten çözüyor: tek nesne olarak range, pipe operatörüyle zincirleme ve lazy evaluation ile sıfır maliyet.
Range Nedir?
Iterator Çiftinden Tek Nesneye
Klasik C++ algoritmaları her zaman iki iterator ister:
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {5, 3, 8, 1, 9, 2, 7};
// Klasik: begin/end çifti
std::sort(v.begin(), v.end());
auto it = std::find(v.begin(), v.end(), 8);
// Her seferinde begin/end yazmak tekrar, hata riski
// Yanlışlıkla farklı container'ların iterator'larını
// karıştırabilirsin — undefined behavior!
}Range kavramı bunu basitleştirir. Bir range, `begin()` ve `end()` sağlayan herhangi bir nesnedir. std::vector, std::string, C dizileri — hepsi birer range'dir. Ranges algoritmaları doğrudan range üzerinde çalışır:
#include <algorithm>
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {5, 3, 8, 1, 9, 2, 7};
// Ranges: doğrudan container
std::ranges::sort(v);
auto it = std::ranges::find(v, 8);
// Daha kısa, daha güvenli — yanlış iterator karıştırma riski yok
}std::ranges::sort(v) dediğinde compiler v.begin() ve v.end() çağrısını otomatik yapar. Ama asıl güç burada değil — view'larda.
Views: Lazy Dönüşüm Katmanları
View Nedir?
View, bir range üzerinde tembel (lazy) bir dönüşüm tanımlar. Veriyi kopyalamaz, değiştirmez — sadece "bu veriye şu açıdan bak" der. Bir view oluşturmak O(1)'dir çünkü hiçbir hesaplama yapılmaz. Hesaplama, view iterate edildiğinde (elemanlar istendiğinde) gerçekleşir.
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> sayilar = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// View oluştur — henüz hiçbir hesaplama yapılmadı!
auto ciftKareler = sayilar
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * n; });
// Şimdi iterate et — hesaplama ŞİMDİ yapılıyor
for (int val : ciftKareler) {
std::cout << val << " ";
}
// Çıktı: 4 16 36 64 100
}Bu kodda ciftKareler bir view'dır. Oluşturulduğunda hiçbir filtreleme veya dönüşüm yapılmaz. for döngüsü ilk elemanı istediğinde view, kaynak range'den elemanları teker teker çeker, filtreler ve dönüştürür. Bu, gereksiz geçici container oluşturma maliyetini tamamen ortadan kaldırır.
Pipe Operatörü (|)
Pipe operatörü (|) view'ları zincirlemenin sözdizimsel şekeridir. Unix pipe'ı gibi düşün — bir komutun çıktısı diğerinin girdisi olur:
// Pipe ile (okunabilir)
auto sonuc = sayilar
| std::views::filter(kosul)
| std::views::transform(donusum)
| std::views::take(5);
// Pipe olmadan (iç içe, okunması zor)
auto sonuc = std::views::take(
std::views::transform(
std::views::filter(sayilar, kosul),
donusum
), 5
);Pipe versiyonu soldan sağa, doğal dil gibi okunur: "sayıları al, filtrele, dönüştür, ilk 5'ini al." İç içe versiyon ise sağdan sola, en içtekinden başlayarak okunur. Pipe açık ara kazanır.
Temel Views
views::filter — Elemanları Süz
Bir predicate (koşul fonksiyonu) vererek sadece koşulu sağlayan elemanları geçirir:
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> sayilar = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Tek sayıları filtrele
for (int n : sayilar | std::views::filter([](int n) { return n % 2 != 0; })) {
std::cout << n << " ";
}
// Çıktı: 1 3 5 7 9
// String filtresi
std::vector<std::string> isimler = {"Ali", "Ayse", "Mehmet", "Ahmet", "Berk"};
auto aIleBaslayanlar = isimler
| std::views::filter([](const std::string& s) { return s[0] == 'A'; });
for (const auto& isim : aIleBaslayanlar) {
std::cout << isim << " ";
}
// Çıktı: Ali Ayse Ahmet
}filter orijinal container'ı değiştirmez. View sadece "bu elemanlara bak" der. Kaynak veri aynen durur.
views::transform — Elemanları Dönüştür
Her elemana bir fonksiyon uygular:
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
struct Ogrenci {
std::string isim;
int puan;
};
int main() {
std::vector<Ogrenci> sinif = {
{"Ali", 85}, {"Ayse", 92}, {"Mehmet", 78}, {"Zeynep", 95}
};
// Sadece isimleri al
auto isimler = sinif
| std::views::transform([](const Ogrenci& o) { return o.isim; });
for (const auto& isim : isimler) {
std::cout << isim << " ";
}
// Çıktı: Ali Ayse Mehmet Zeynep
// Puanları 10 üzerinden göster
auto notlar = sinif
| std::views::transform([](const Ogrenci& o) {
return o.puan / 10.0;
});
for (double not_ : notlar) {
std::cout << not_ << " ";
}
// Çıktı: 8.5 9.2 7.8 9.5
}views::take ve views::drop — Dilimle
take(n) ilk n elemanı alır, drop(n) ilk n elemanı atlar:
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {10, 20, 30, 40, 50, 60, 70, 80};
// İlk 3
for (int n : v | std::views::take(3)) {
std::cout << n << " ";
}
// Çıktı: 10 20 30
// İlk 3'ü atla
for (int n : v | std::views::drop(3)) {
std::cout << n << " ";
}
// Çıktı: 40 50 60 70 80
// Birleştir: 3'ü atla, sonraki 2'yi al (slice gibi)
for (int n : v | std::views::drop(3) | std::views::take(2)) {
std::cout << n << " ";
}
// Çıktı: 40 50
}take ve drop lazy'dir. take(3) dediğinde 3 eleman ötesine hiç bakılmaz — kaynak range 1 milyon elemanlı olsa bile sadece 3 eleman okunur.
views::iota — Sayı Dizisi Üreteci
iota bir başlangıç değerinden itibaren artan sayı dizisi üretir. Sonsuz veya sınırlı olabilir:
#include <ranges>
#include <iostream>
int main() {
// Sınırlı: 1'den 10'a (10 dahil değil)
for (int n : std::views::iota(1, 11)) {
std::cout << n << " ";
}
// Çıktı: 1 2 3 4 5 6 7 8 9 10
// Sonsuz: 0'dan sonsuza — take ile sınırla!
for (int n : std::views::iota(0) | std::views::take(5)) {
std::cout << n << " ";
}
// Çıktı: 0 1 2 3 4
// Pratik: Python'daki range(1, 101) benzeri
auto topla = [](auto range) {
int toplam = 0;
for (int n : range) toplam += n;
return toplam;
};
std::cout << topla(std::views::iota(1, 101));
// Çıktı: 5050 (1+2+...+100)
}iota herhangi bir container oluşturmaz. Sayılar talep edildikçe üretilir — bellek kullanımı O(1).
views::split ve views::join
split — Böl
Bir range'i belirli bir ayırıca (delimiter) göre parçalara böler:
#include <ranges>
#include <string>
#include <iostream>
int main() {
std::string csv = "Ali,85,Ankara,Muhendis";
for (auto parca : csv | std::views::split(',')) {
// parca bir sub-range — string'e dönüştürmek gerekir
std::string s(parca.begin(), parca.end());
std::cout << "[" << s << "] ";
}
// Çıktı: [Ali] [85] [Ankara] [Muhendis]
// String olarak split
std::string metin = "Merhaba--Dunya--CPP";
std::string ayirici = "--";
for (auto parca : metin | std::views::split(ayirici)) {
std::string s(parca.begin(), parca.end());
std::cout << s << " ";
}
// Çıktı: Merhaba Dunya CPP
}join — Birleştir
İç içe range'leri düzleştirir (flatten):
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
int main() {
std::vector<std::vector<int>> matris = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 2D → 1D
for (int n : matris | std::views::join) {
std::cout << n << " ";
}
// Çıktı: 1 2 3 4 5 6 7 8 9
// String vektörü → tek karakter dizisi
std::vector<std::string> kelimeler = {"Merhaba", " ", "Dunya"};
for (char c : kelimeler | std::views::join) {
std::cout << c;
}
// Çıktı: Merhaba Dunya
}split ve join birbirinin tersidir. split böler, join birleştirir. İkisi de lazy çalışır.
Klasik Algorithm vs Ranges Algorithm
Karşılaştırma
Aynı işi iki şekilde yazalım — "bir öğrenci listesinden 80 üstü puanı olan ilk 3 öğrencinin ismini al":
#include <algorithm>
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
struct Ogrenci {
std::string isim;
int puan;
};
int main() {
std::vector<Ogrenci> sinif = {
{"Ali", 75}, {"Ayse", 92}, {"Mehmet", 88},
{"Zeynep", 95}, {"Berk", 65}, {"Deniz", 82}
};
// --- KLASİK YAKLAŞIM ---
// 1. Filtrele → geçici container
std::vector<Ogrenci> basarili;
std::copy_if(sinif.begin(), sinif.end(),
std::back_inserter(basarili),
[](const Ogrenci& o) { return o.puan > 80; });
// 2. İlk 3'ünü al → dikkatli olmalısın
int limit = std::min<int>(3, basarili.size());
// 3. İsimlerini al → başka geçici container
std::vector<std::string> isimler;
std::transform(basarili.begin(), basarili.begin() + limit,
std::back_inserter(isimler),
[](const Ogrenci& o) { return o.isim; });
for (const auto& isim : isimler) {
std::cout << isim << " ";
}
// 2 geçici container, 3 ayrı adım, çok fazla boilerplate
std::cout << std::endl;
// --- RANGES YAKLAŞIMI ---
auto sonuc = sinif
| std::views::filter([](const Ogrenci& o) { return o.puan > 80; })
| std::views::transform([](const Ogrenci& o) { return o.isim; })
| std::views::take(3);
for (const auto& isim : sonuc) {
std::cout << isim << " ";
}
// 0 geçici container, tek ifade, lazy evaluation
// Çıktı: Ayse Mehmet Zeynep
}Ranges versiyonu daha kısa ve okunabilir. Ama asıl avantaj performansta: klasik yaklaşımda tüm öğrenciler filtrelenir ve geçici container'a kopyalanır. Ranges yaklaşımında ise sadece ilk 3 başarılı öğrenci bulunana kadar iterate edilir — geri kalanına bakılmaz bile.
Projection Desteği
Ranges algoritmaları projection destekler — elemanlara doğrudan erişmek yerine bir dönüşüm fonksiyonu verebilirsin:
#include <algorithm>
#include <ranges>
#include <vector>
#include <string>
#include <iostream>
struct Ogrenci {
std::string isim;
int puan;
};
int main() {
std::vector<Ogrenci> sinif = {
{"Ali", 75}, {"Ayse", 92}, {"Mehmet", 88}, {"Zeynep", 95}
};
// Klasik: custom comparator yazmalısın
std::sort(sinif.begin(), sinif.end(),
[](const Ogrenci& a, const Ogrenci& b) {
return a.puan < b.puan;
});
// Ranges: projection ile
std::ranges::sort(sinif, std::less{}, &Ogrenci::puan);
// "Ogrenci'nin puan'ına göre, küçükten büyüğe sırala"
// İsme göre sıralama
std::ranges::sort(sinif, {}, &Ogrenci::isim);
// {} = default comparator (std::less), isim alanına projection
// En yüksek puanlıyı bul
auto en_iyi = std::ranges::max(sinif, {}, &Ogrenci::puan);
std::cout << en_iyi.isim << ": " << en_iyi.puan << std::endl;
// Çıktı: Zeynep: 95
}Projection, lambda yazmak yerine doğrudan member pointer vererek kodu çok daha temiz tutar. Özellikle struct/class vektörleriyle çalışırken büyük fark yaratır.
Lazy Evaluation Avantajı
Neden Lazy Önemli?
Eager (hevesli) evaluation'da her adım tüm veriyi işler ve geçici sonuç üretir. Lazy evaluation'da ise sadece talep edilen kadar hesaplama yapılır:
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> buyukVeri(1'000'000);
// 0'dan 999999'a kadar doldur
std::iota(buyukVeri.begin(), buyukVeri.end(), 0);
// Lazy: sadece ilk 3 sonuç hesaplanır
auto sonuc = buyukVeri
| std::views::filter([](int n) {
// Bu lambda kaç kez çağrılır?
return n % 100'000 == 0;
})
| std::views::transform([](int n) {
return n * n;
})
| std::views::take(3);
for (auto val : sonuc) {
std::cout << val << " ";
}
// Çıktı: 0 10000000000 40000000000
// filter lambdası ~200.001 kez çağrıldı (ilk 3 sonucu bulana kadar)
// Eager olsaydı: 1.000.000 filter + 10 transform + 3 copy = daha fazla iş
}Lazy evaluation özellikle şu durumlarda parlıyor:
Büyük veri setlerinde sadece küçük bir alt küme gerektiğinde
Pahalı dönüşümlerin gereksiz yere tüm elemanlara uygulanmasını önlemek
Sonsuz dizilerle çalışmak (iota ile sayılar, dosya satırları vs.)
💡 İpucu: Views composable'dır — birden fazla view'ı pipe ile bağladığında ara sonuçlar için hiçbir bellek ayrılmaz. Tek bir for döngüsü yazmışsın gibi derlenir. Bu zero-overhead abstraction ilkesidir: kullanmadığın için ödeme yapmazsın, kullandığın için de elle yazdığından fazla ödemezsin.
Custom View Yazma
Standart view'lar çoğu ihtiyacı karşılar ama bazen kendi view'ını yazmak gerekir. C++20'de en kolay yol std::ranges::view_interface kullanmaktır:
#include <ranges>
#include <vector>
#include <iostream>
// Belirli aralıktaki elemanları alan view (stride/step)
// Her n'inci elemanı al (views::stride C++23'te)
template <std::ranges::input_range R>
class StepView : public std::ranges::view_interface<StepView<R>> {
R base_;
int step_;
public:
StepView() = default;
StepView(R base, int step) : base_(std::move(base)), step_(step) {}
class iterator {
std::ranges::iterator_t<R> current_;
std::ranges::sentinel_t<R> end_;
int step_;
public:
using value_type = std::ranges::range_value_t<R>;
using difference_type = std::ptrdiff_t;
iterator() = default;
iterator(std::ranges::iterator_t<R> c,
std::ranges::sentinel_t<R> e, int s)
: current_(c), end_(e), step_(s) {}
value_type operator*() const { return *current_; }
iterator& operator++() {
for (int i = 0; i < step_ && current_ != end_; ++i) {
++current_;
}
return *this;
}
iterator operator++(int) { auto tmp = *this; ++*this; return tmp; }
bool operator==(const std::ranges::sentinel_t<R>& s) const {
return current_ == s;
}
bool operator!=(const std::ranges::sentinel_t<R>& s) const {
return current_ != s;
}
};
auto begin() { return iterator(std::ranges::begin(base_),
std::ranges::end(base_), step_); }
auto end() { return std::ranges::end(base_); }
};
// Deduction guide
template <class R>
StepView(R&&, int) -> StepView<std::views::all_t<R>>;
int main() {
std::vector<int> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for (int n : StepView(v, 3)) {
std::cout << n << " ";
}
// Çıktı: 0 3 6 9
}Custom view yazmak kolay değil — iterator requirements, sentinel kavramı ve view_interface beklentileri var. Çoğu durumda standart view'ları pipe ile birleştirerek ihtiyacını karşılayabilirsin. Custom view, standart view'ların yetersiz kaldığı özel durumlara saklansın.
⚠️ Dikkat: C++23 ile views::stride standarta girdi. Yukarıdaki örnek eğitim amaçlıdır. Pratikte mümkün olduğunca standart view'ları tercih et — test edilmiş, optimize edilmiş ve compiler'lar tarafından iyi anlaşılıyorlar.
ranges::to — Container'a Dönüştürme (C++23)
C++20'de bir view'ı container'a dönüştürmek zahmetli. C++23 bunu ranges::to ile çözüyor:
#include <ranges>
#include <vector>
#include <string>
#include <set>
#include <iostream>
int main() {
// C++20: view → container (zahmetli)
auto view20 = std::views::iota(1, 11)
| std::views::filter([](int n) { return n % 2 == 0; });
// Eski yol: manual construction
std::vector<int> v20(view20.begin(), view20.end());
// C++23: ranges::to (temiz!)
auto v23 = std::views::iota(1, 11)
| std::views::filter([](int n) { return n % 2 == 0; })
| std::ranges::to<std::vector>();
// v23 = {2, 4, 6, 8, 10}
// Farklı container tiplerine
auto s = std::views::iota(1, 11)
| std::views::transform([](int n) { return n * n; })
| std::ranges::to<std::set>();
// s = {1, 4, 9, 16, 25, 36, 49, 64, 81, 100}
// String dönüşümü
std::string kaynak = "Merhaba Dunya";
auto buyukHarfler = kaynak
| std::views::transform([](char c) { return std::toupper(c); })
| std::ranges::to<std::string>();
// buyukHarfler = "MERHABA DUNYA"
}ranges::to pipeline'ın son halkasıdır. View'lar lazy, ranges::to ise eager — "tamam, şimdi gerçekten hesapla ve bir container'a koy" der. GCC 14+, Clang 17+ ve MSVC 17.8+ ile kullanılabilir.
Performans: Zero-Overhead Abstraction
Compiler Optimizasyonu
View'lar soyutlama katmanı ekler ama derlenmiş kodda bu katman genellikle tamamen ortadan kalkar. Modern compiler'lar view pipeline'ını, elle yazılmış döngüyle aynı makine koduna derler:
#include <ranges>
#include <vector>
#include <numeric>
// View versiyonu
int toplamRanges(const std::vector<int>& v) {
int toplam = 0;
for (int n : v | std::views::filter([](int n) { return n > 0; })
| std::views::transform([](int n) { return n * 2; })) {
toplam += n;
}
return toplam;
}
// Elle yazılmış döngü versiyonu
int toplamManuel(const std::vector<int>& v) {
int toplam = 0;
for (int n : v) {
if (n > 0) {
toplam += n * 2;
}
}
return toplam;
}
// Her iki fonksiyon da -O2 ile AYNI makine koduna derlenir.
// godbolt.org'da kendin kontrol edebilirsin.Bu zero-overhead abstraction'dır. Range pipeline yazarken okunabilirlik, composability ve güvenlik kazanırsın — performans kaybetmezsin.
View Kopyalama Maliyeti
View'lar hafiftir. Bir view, kaynak range'e referans ve birkaç lambda tutar — veriyi kopyalamaz. Bu yüzden view'ı kopyalamak, fonksiyona geçmek, saklamak ucuzdur:
#include <ranges>
#include <vector>
#include <iostream>
void viewYazdir(auto view) { // View kopyalanır — ucuz!
for (auto val : view) {
std::cout << val << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto tekler = v | std::views::filter([](int n) { return n % 2 != 0; });
auto kareler = v | std::views::transform([](int n) { return n * n; });
viewYazdir(tekler); // 1 3 5 7 9
viewYazdir(kareler); // 1 4 9 16 25 36 49 64 81 100
// sizeof(tekler) tipik olarak birkaç pointer boyutunda
}⚠️ Dikkat: View'lar kaynak range'e referans tutar. Kaynak range yok edildikten sonra view'ı kullanmak undefined behavior'dır. View'ların ömrü, kaynak range'in ömründen kısa olmalıdır. Owning view istiyorsan std::ranges::owning_view veya ranges::to kullan.
Gerçek Dünya Örneği: CSV İşleme Pipeline
Birden fazla view'ı birleştiren pratik bir örnek:
#include <ranges>
#include <string>
#include <string_view>
#include <vector>
#include <iostream>
#include <sstream>
#include <algorithm>
int main() {
// Basit CSV verisi
std::string csv =
"isim,puan,sehir\n"
"Ali,85,Ankara\n"
"Ayse,92,Istanbul\n"
"Mehmet,78,Izmir\n"
"Zeynep,95,Istanbul\n"
"Berk,65,Ankara\n"
"Deniz,88,Istanbul\n";
// Satırlara böl, başlığı atla, puanı 80+ olanları filtrele
auto satirlar = csv | std::views::split('\n');
bool ilkSatir = true;
for (auto satir : satirlar) {
std::string s(satir.begin(), satir.end());
if (s.empty()) continue;
if (ilkSatir) {
ilkSatir = false;
continue; // Başlık satırını atla
}
// Virgülle böl ve puanı kontrol et
auto alanlar = s | std::views::split(',');
auto it = alanlar.begin();
std::string isim((*it).begin(), (*it).end()); ++it;
std::string puanStr((*it).begin(), (*it).end()); ++it;
std::string sehir((*it).begin(), (*it).end());
int puan = std::stoi(puanStr);
if (puan >= 80) {
std::cout << isim << " (" << sehir << "): " << puan << std::endl;
}
}
// Çıktı:
// Ali (Ankara): 85
// Ayse (Istanbul): 92
// Zeynep (Istanbul): 95
// Deniz (Istanbul): 88
}Bu örnek ranges'in string işleme gücünü gösteriyor. Hiçbir geçici vector oluşturulmadan, veriler satır satır ve alan alan işleniyor.
Yaygın Hatalar
1. Dangling View
// YANLIS: Geçici nesne üzerinde view
auto tehlikeli() {
return std::vector{1, 2, 3} | std::views::take(2);
// Geçici vector yok edildi, view dangling referans tutuyor!
}
// DOGRU: Verinin sahibi view'dan uzun yaşamalı
std::vector<int> veri = {1, 2, 3};
auto guvenli = veri | std::views::take(2); // veri yaşadığı sürece OK2. View'ın Tembel Olduğunu Unutmak
std::vector<int> v = {1, 2, 3, 4, 5};
auto view = v | std::views::transform([](int n) { return n * 2; });
v[0] = 100; // Orijinal veriyi değiştir
// view ŞİMDİ iterate edilirse, v[0] = 100 olarak görünür!
for (int n : view) std::cout << n << " ";
// Çıktı: 200 4 6 8 10 (100*2 = 200!)
// View veriyi kopyalamadı, her iterate'te tekrar hesaplarView'lar veriyi kopyalamaz, sadece referans tutar. Veriyi değiştirirsen view'ın sonucu da değişir. Bu bazen istenen bir davranış, bazen sürpriz olabilir.
Özet
Range,
begin()veend()sağlayan tek bir nesnedir — iterator çiftleri yerine doğrudan container'ı algoritmaya geçirebilirsin.Views, range üzerinde lazy dönüşüm katmanlarıdır — veriyi kopyalamaz, sadece "bu açıdan bak" der. Oluşturmak O(1)'dir.
Pipe operatörü (|) ile view'lar zincirlenir —
data | filter | transform | takeşeklinde okunabilir pipeline'lar yazılır.Lazy evaluation sayesinde sadece ihtiyaç duyulan kadar hesaplama yapılır — 1 milyon elemanlı veri setinden ilk 3'ünü almak, 3 eleman maliyetindedir.
Ranges algoritmaları projection desteğiyle struct alanlarına doğrudan erişim sağlar —
std::ranges::sort(v, {}, &Ogrenci::puan)gibi temiz sözdizimi.Zero-overhead abstraction: View pipeline'ları, derlenmiş kodda elle yazılmış döngülerle aynı performansı verir — compiler soyutlamayı tamamen ortadan kaldırır.
AI Asistan
Sorularını yanıtlamaya hazır