C++ Dilinde std::range
- Yusuf Hançar

- Dec 27, 2025
- 67 min read
C++20 ile gelen Ranges Library, standart algoritma ve iterator kütüphanelerinin doğal bir evrimidir. Temel amaç, algoritmaların daha okunabilir, daha güvenli ve birbirine zincirlenebilir (composable) şekilde kullanılmasını sağlamaktır. Klasik STL algoritmalarında begin() ve end() çağrılarını manuel vermek gerekirken, ranges ile koleksiyonun kendisi doğrudan algoritmaya geçirilebilir. Böylece hem hata olasılığı azalır hem de kodun niyeti daha net ifade edilir.
Ranges aynı zamanda concepts ile sıkı şekilde ilişkilidir. Yani birçok algoritma, derleme zamanı seviyesinde belirli kısıtlamalara (constraints) sahiptir. Bu sayede yanlış türler veya uygunsuz iterator kategorileriyle algoritmayı çalıştırmaya çalıştığınızda, hatayı ancak çalışma zamanında değil, derleme anında görürsünüz. Bazı özel durumlarda bu kısıtlamalar requires ifadesi ya da deneysellik aşamasında requires experimental şeklinde tanımlanmıştır. Bu da geliştiriciye güçlü bir güvenlik ağı sağlar.
Özetle, Ranges Library şu faydaları getirir:
Daha anlaşılır sözdizimi: std::ranges::for_each(vec, f); gibi doğrudan container ile kullanılabilir.
Birleştirilebilirlik (composability): views::filter, views::transform gibi "pipeline" mantığıyla zincirleme yapılabilir.
Derleme zamanı güvenliği: Concepts ve requires clause sayesinde, algoritmalar yalnızca doğru türlerle çalışır.
Modern C++ paradigmasına uyum: Fonksiyonel tarzdaki veri işleme akışlarıyla C++’ı daha güçlü kılar.
Dolayısıyla ranges, STL algoritmalarının ve iterator altyapısının modern bir genişlemesi olup, daha az hata, daha fazla ifade gücü ve daha güçlü tip güvenliği sağlar.
#include <algorithm>
#include <iostream>
#include <iterator>
#include <ranges>
#include <string>
#include <vector>
int main()
{
std::vector<std::string> words
{ "Moon", "Mercury", "Venus", "Sun", "Mars", "Jupiter", "Earth", "Saturn" };
// ranges::sort: random_access_range + sortable constraint (compile-time)
std::ranges::sort(words);
// klasik: begin/end ile copy
std::copy(words.begin(), words.end(), ostream_iterator<string>{cout, "\n"});
}
*****************************
AÇIKLAMA :
*****************************
CEVAP :
Earth
Jupiter
Mars
Mercury
Moon
Saturn
Sun
Venus
*****************************#include <algorithm>
#include <iostream>
#include <iterator>
#include <ranges>
#include <string>
#include <vector>
int main()
{
std::vector<std::string> words
{ "Moon", "Mercury", "Venus", "Sun", "Mars", "Jupiter", "Earth", "Saturn" };
std::ranges::sort(space);
// tam ranges: aralığın kendisini veriyoruz
std::ranges::copy(space, ostream_iterator<string>{cout, "\n"});
}
*****************************
AÇIKLAMA : concept contraint oldu.
*****************************
CEVAP :
Earth
Jupiter
Mars
Mercury
Moon
Saturn
Sun
Venus
*****************************int main()
{
std::vector<std::string> space
{ "Moon", "Mercury", "Venus", "Sun", "Mars", "Jupiter", "Earth", "Saturn" };
std::ranges::sort(space);
auto upper = [](std::string str) {
for (char& ch : str)
{
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
}
return str;
};
auto pipeline =
space
| std::views::filter([](const std::string& str){ return str.size() >= 4; })
| std::views::transform(upper)
| std::views::take(5);
std::ranges::copy(pipeline, std::ostream_iterator<std::string>{std::cout, "\n"});
}
*****************************
AÇIKLAMA : 4 ve üzeri harfli kelimeleri al, büyük harfe çevir ve ilk 5 tanesini yaz.
*****************************
CEVAP : ranges::sort ve ranges::copy ile concept-constrained güvenlik (yanlış kullanım compile time da yakalanır).
views::filter | views::transform | views::take ile zincirlenebilir ve ara kopyasız akış.
begin/end kullanmadan, net ve temiz bir sözdizimi.
*****************************std::ranges
namespace std {
namespace views = ranges::views;
}template <std::ranges::range T>
void func(T&& t); // parametre universal reference
int main()
{
func(45); // "no matching function" (constraint failure)
func(std::string{ "smartcode" }); // legal. range concept'i karşılıyor.
}
*****************************
AÇIKLAMA : "ranges" namespace ismidir. range ise concept ismidir.
*****************************
CEVAP : func(45) compile time error olarak yakalanır.
*****************************template <std::ranges::range T>
void func(T&& t){}
int main()
{
int arr[10]{};
func(arr);
}
*****************************
AÇIKLAMA : bir dizi de range concepti karşılar.
*****************************
CEVAP : legal
*****************************template <std::ranges::range T>
void func(T&& t){}
int main()
{
func("smartcode"sv);
}
*****************************
AÇIKLAMA : bir string_view de range concepti karşılar.
*****************************
CEVAP : legal
*****************************template <std::ranges::range T>
void func(T&&) {}
int main()
{
const char arr[] = "smartcode";
func(arr); // dizi: range
const char* ptr = "smartcode";
func(ptr); // pointer tek başına range DEĞİL → constraints not satisfied
}Abbreviated function syntax ile
return_type function_name ( auto paramter_name);void func(std::ranges::range auto&&){}
int main()
{
func("smartcode"sv);
}
*****************************
CEVAP : legal
*****************************Bu conceptler arasında da bir hiyerarşi vardır.
diğer tüm range conceptleri range conceptini subsume eder.
void func(std::ranges::range auto&&){}
void func(std::ranges::input_range auto&&){}
int main()
{
std::vector<int> ivec;
func(ivec); // subsume olduğu için legal
}
*****************************
AÇIKLAMA : overloading var.
input_range, range conceptini subsume eder.
*****************************
CEVAP : legal
*****************************void func(std::ranges::range auto)
{
std::cout << "range\n";
}
void func(std::ranges::input_range auto)
{
std::cout << "input_range\n";
}
void func(std::ranges::forward_range auto)
{
std::cout << "forward_range\n";
}
void func(std::ranges::bidirectional_range auto)
{
std::cout << "bidirectional_range\n";
}
void func(std::ranges::random_access_range auto)
{
std::cout << "random_access_range\n";
}
void func(std::ranges::contiguous_range auto)
{
std::cout << "contiguous_range\n";
}
using uv = std::vector<int>;
using dv = std::deque<int>;
using lv = std::list<int>;
using fv = std::forward_list<int>;
int main()
{
func(uv{}); // contiguous_range
func(dv{}); // random_access_range
func(lv{}); // bidirectional_range
func(fv{}); // forward_range
}
*****************************
CEVAP :
contiguous_range
random_access_range
bidirectional_range
forward_range
*****************************forward_list size fonksiyonu yoktur bunun yerine distance çağırılır. (HATIRLATMA)
int main()
{
std::forward_list<int> sc(5);
std::cout << distance(sc.begin(), sc.end());
}std::ranges::sized_range<R> konsepti, bir aralık için std::ranges::size(r) ifadesinin derleme zamanında geçerli olmasını şart koşar.
int main()
{
static_assert(std::ranges::sized_range<std::forward_list<int>>); // error
static_assert(std::ranges::sized_range<std::list<int>>); // legal
}
*****************************
AÇIKLAMA : forward_list size fonksiyonu yoktur
***************************** void func(std::ranges::range auto)
{
std::cout << "range\n";
}
void func(std::ranges::input_range auto)
{
std::cout << "input_range\n";
}
void func(std::ranges::forward_range auto)
{
std::cout << "forward_range\n";
}
void func(std::ranges::bidirectional_range auto)
{
std::cout << "bidirectional_range\n";
}
void func(std::ranges::random_access_range auto)
{
std::cout << "random_access_range\n";
}
void func(std::ranges::contiguous_range auto)
{
std::cout << "contiguous_range\n";
}
void func(std::ranges::sized_range auto) // constant time da size'ı elde edilebilecek.
{
std::cout << "sized_range\n";
}
using uv = std::vector<int>;
int main()
{
func(uv{}); // error
}
*****************************
AÇIKLAMA : sized_range ile çakıştı.
uv = std::vector<int> aynı anda contiguous_range, random_access_range, bidirectional_range, forward_range, input_range ve sized_range’dir
Bu yüzden birden fazla func(...) overload uygun olur ve özellikle contiguous_range ile sized_range birbirini subsume etmediği için çağrı ambiguous kalır ⇒ derleme hatası.
*****************************
CEVAP :
*****************************Çözüm 1 :
template <std::ranges::range T>
void func(T&&)
{
if constexpr (std::ranges::contiguous_range<T>)
{
std::cout << "contiguous_range";
}
else if constexpr (std::ranges::random_access_range<T>)
{
std::cout << "random_access_range";
}
else if constexpr (std::ranges::bidirectional_range<T>)
{
std::cout << "bidirectional_range";
}
else if constexpr (std::ranges::forward_range<T>)
{
std::cout << "forward_range";
}
else if constexpr (std::ranges::input_range<T>)
{
std::cout << "input_range";
}
else
{
std::cout << "range";
}
if constexpr (std::ranges::sized_range<T>)
{
std::cout << " + sized_range";
}
std::cout << '\n';
}
using uv = std::vector<int>;
int main()
{
func(uv{});
}
*****************************
CEVAP : contiguous_range + sized_range
*****************************Çözüm 2 :
void func(std::ranges::range auto)
{
std::cout << "range\n";
}
void func(std::ranges::sized_range auto)
{
std::cout << "sized_range\n";
}
using uv = std::vector<int>;
int main()
{
func(uv{});
}
*****************************
CEVAP : sized_range
*****************************range concept'ine ait olmayan STL deki diğer algoritmalarda begin ve end aynı türden olduğundan end() konumu için baştan sona gezilir ve algoritma sonra tekrar begin() ve end() döngüsü yapar.
Eski algoritmalarda fonksiyona null karakterin konumunu geçmek zorundaydık. Bunun yerine begin ve end türlerini farklı yazabiliriz.
struct NT {
bool operator==(auto sc) const // abbreviated syntax
{
return *sc == '\0';
}
};
template <class I, class S>
requires std::sentinel_for<S, I>
void print(I beg, S end)
{
while (beg != end) {
std::cout << *beg << ' ';
++beg;
}
std::cout << '\n';
}
int main()
{
char str[50] = "smart code";
print(str, NT{});
}class Data {};
int main()
{
auto val = Data;
}
*****************************
AÇIKLAMA : Data bir ifade değildir. auto val = Data{}; şeklinde yazarsak geçerli olur.
*****************************
CEVAP : syntax error
*****************************int main()
{
std::cout << typeid(std::ranges::sort).name() << "\n";
}
*****************************
AÇIKLAMA : türü object
***************************** int main()
{
std::ranges::sort
}
*****************************
AÇIKLAMA :
std::ranges::sort (ve diğer ranges algoritmaları) serbest fonksiyon şablonu değil, sınıf türünden, inline constexpr bir nesnedir. Çağrıda aslında o nesnenin operator() şablonları devreye girer. Yani “şablon” hâlâ var, ama operator() üzerindedir.
- Birden çok operator() overload’ı bulunur:
- iterator/sentinel çifti alan sürümler,range alan sürümler,
- ayrıca karşılaştırıcı (comp) ve projeksiyon (proj) parametreli varyantlar.
Her overload concept’lerle kısıtlanmıştır (ör. sort için random_access_range + sortable). Yanlış aralıkla denenirse, adaylar constraint’ten elenir → derleme zamanı hatası olur.
Nesne oluşu sayesinde algoritmayı yüksek seviyeli kullanmak kolaylaşır (parametre olarak geçme, saklama, std::invoke ile çağırma vb.).
-> std::ranges::sort’un kendisi inline constexpr bir nesnedir; bu, “algoritma her zaman constexpr bağlamda çalışır” demek değildir. (C++ sürümüne göre algoritmanın constexpr çalışabilirliği ayrı kurallara bağlıdır.) Burada “constexpr” vurgusu, nesnenin tek tanımlı ve ODR-dostu olmasını sağlar.
std::ranges::begin/end/size/... gibi CPO (customization point object) olanlar da inline constexpr nesnedir; ama algoritmalar (sort, find, …) “kullanıcı ADL’siyle özelleştirilsin” diye değil, çağrılabilir nesne olarak tasarlanmıştır. Özelleştirme, range/iterator türlerinizin gereken concept’leri (örn. sortable) sağlamasıyla olur; algoritmanın davranışını ADL ile “yeniden yazmazsınız”.
***************************** Rangified algorithms https://en.cppreference.com/w/Talk%253Acpp/algorithm/ranges.html
ranges namespace içindedirler
Klasik STL algoritmalarının “range sürümleri” std::ranges:: namespace içinde yer alır (ör. std::ranges::sort, std::ranges::copy).
Çağrılabilir nesnelerdir
Serbest fonksiyon şablonu değil; inline constexpr function object’lerdir. Çağrı operator()’ün concept’lerle kısıtlanmış overload’larına gider.
Parametre biçimi farklıdır (range-first)
Range alan overload:
std::ranges::sort(R&& r, Comp comp = {}, Proj proj = {})
Iterator/Sentinel alan overload:
std::ranges::sort(I first, S last, Comp = {}, Proj = {})
Birçok algoritma comp ve proj (projection) parametrelerini destekler;
default std::ranges::less ve std::identity.
Sonuç türleri yapı (result) döndürür
copy gibi algoritmalar tek iterator yerine çiftleri içeren sonuçlar döndürür
auto res = std::ranges::copy(src, dst);
// res.in, res.outConcept tabanlı güvenlik
Algoritmalar doğru aralık/türlerde çalışır (örn. sort için random_access_range + sortable). Yanlış kullanım derlemede elenir.
Sentinel desteği
begin ve end aynı tür olmak zorunda değil; sentinel ile bitiş koşulu tanımlanabilir.
Paralel yürütme yok
Mevcut standartta std::execution politikalarıyla ranges algoritmalarının ayrı paralel overload’ları yoktur (klasik <algorithm> tarafında var).
Numeric algoritmaların durumu
<numeric>’teki klasiklerin büyük kısmı rangified değil (accumulate, inner_product, partial_sum vb.).
std::counted_iterator
template< std::input_or_output_iterator I >
class counted_iterator;int main()
{
std::vector<int> ivec{ 3, 6, 8, 9, 1, 7 };
std::counted_iterator iter{ ivec.begin(), 3 };
std::cout << iter.count() << "\n"; // kalanı kadarını döndürür -> 3
++iter;
std::cout << iter.count(); // kalanı kadarını döndürür -> 2
}
*****************************
AÇIKLAMA : kaç defa artırılabileceği bilgisini kendi içinde saklayan bir algoritmadır.
yani iter bir bir başka iteratoru karşılaştırmamıza gerek yok
*****************************
CEVAP :
3
2
*****************************int main()
{
std::vector<int> ivec{3, 6, 8, 9, 1, 7};
auto first_three = std::views::counted(ivec.begin(), 3);
std::ranges::copy(first_three, std::ostream_iterator<int>{std::cout, " "});
}
*****************************
AÇIKLAMA : Ranges ile
*****************************
CEVAP : 3 6 8
*****************************ranges::subrange
range içindeki başka bir range ifadesidir.(C++20)
bir iterator ve bir sentinel'i tek bir görünümde birleştirir.
template<
std::input_or_output_iterator I,
std::sentinel_for<I> S = I,
ranges::subrange_kind K = std::sized_sentinel_for<S, I> ?
ranges::subrange_kind::sized :
ranges::subrange_kind::unsized >
requires (K == ranges::subrange_kind::sized || !std::sized_sentinel_for<S, I>)
class subrange
: public ranges::view_interface<subrange<I, S, K>>“maximum subarray / maximum subsequence (contiguous)” sorusu ile başlayalım :
Çözüm: Kadane algoritması, karmaşıklık: O(n).
#include <iostream>
#include <vector>
#include <ranges>
using namespace std;
int main()
{
vector<int> ivec{ 1, -5, 4, -2, 6, -3, 7, 2, -8 };
int best_sum = ivec[0];
int cur_sum = 0;
size_t best_start = 0;
size_t best_end = 0;
size_t cur_start = 0;
for (size_t i = 0; i < ivec.size(); ++i)
{
if (cur_sum <= 0) {
cur_sum = ivec[i];
cur_start = i;
}
else
{
cur_sum += ivec[i];
}
if (cur_sum > best_sum)
{
best_sum = cur_sum;
best_start = cur_start;
best_end = i; // dahil
}
}
std::ranges::subrange sub(ivec.begin() + best_start,
ivec.begin() + best_end + 1);
cout << "max sum : " << best_sum << "\n";
cout << "subrange : ";
for (int x : sub)
{
cout << x << ' ';
}
cout << '\n';
}
*****************************
AÇIKLAMA : En büyük toplamı veren contiguous subsequence (subarray) nedir?”
Kadane algoritması:
-> cur_sum <= 0 olduğunda, yeni bir subsequence başlat,
-> aksi halde mevcut subsequence’i büyüt,
-> her adımda en iyi (max) toplamı güncelle.
Karmaşıklık: O(n).
Burada ranges::subrange ile:
-> best_start ve best_end indeksleri arasındaki alt diziyi temsil ediyoruz:
subrange(ivec.begin() + best_start, ivec.begin() + best_end + 1)
-> Yani “range içindeki başka bir range” mantığı tam da bu.
*****************************
CEVAP : En büyük toplam: 14
Subsequence : 4, -2, 6, -3, 7, 2
(index aralığı : 2..7 dahil)
*****************************int main()
{
vector ivec{ 1, 2, 3, 4, 5 };
std::ranges::subrange sr1{ ivec }; // CTAD
// subrange is a view
cout << "sizeof(sr1) : " << sizeof(sr1) << "\n";
static_assert(ranges::view<decltype(sr1)>);
/* 1 std::ranges::subrange<...> std::ranges::view_interface<subrange<...>>’dan türeyerek,
view_interface içindeki hazır yardımcı fonksiyonları
(ör. front(), back(), empty(), operator[] gibi) “bedavaya” alır.
*/
auto& r1 = sr1.front();
// 5
auto& r2 = sr1.back();
cout << r1 << " " << r2 << "\n";
ranges::subrange sr2{ ivec.begin(), ivec.end() };
for (const auto i : sr2)
{
cout << i << " ";
}
cout << std::format("\n{}\n{}\n", sr1, sr2);
}
*****************************
AÇIKLAMA :
C++20 ile lvalue bir range verdiğin için subrange bunun begin/end iteratörlerini alıp kendi içinde saklar. Buradaki CTAD ile şuna benzer bir tip çıkar: std::ranges::subrange<std::vector<int>::iterator>
** subrange elemanları kopyalamaz.
** İçinde tipik olarak (begin iterator, end sentinel) saklar (ve bazen boyutu da saklayabilir; store-size seçeneğine bağlı).
->Bu yüzden sizeof(sr1) genelde küçük çıkar (çoğu implementasyonda “2 pointer” gibi düşünebilirsin ama kesin byte değeri implementasyona bağlıdır).
----------------------------------------------------------------
-> front() her view’da otomatik gelmiyor; range konsept şartlarına bağlı.
-> back() daha da sıkı şart ister (genellikle bidirectional + common_range gibi).
Bu örnekte vector iteratörleri bu şartları sağladığı için front/back kullanılabiliyor.
----------------------------------------------------------------
View = “non-owning / referans benzeri adaptör”
-> Çoğu view (özellikle subrange, filter_view, transform_view, take_view, drop_view…) şunu yapar:
** Veriyi sahiplenmez (own etmez)
** Sadece başka bir range’e erişim sağlayan küçük bir nesnedir (iterator/predicate/functor gibi şeyleri tutar)
** Kopyalanması ucuzdur, ama kopyalamak “elemanları çoğaltmak” değildir
----------------------------------------------------------------
-> Bu yüzden “reference semantics” denmesi şunu anlatır:
** auto a = sr1; auto b = sr1; yaptığında iki view da aynı ivec elemanlarına bakar
** ivec[0] = 42; dersen sr1.front() artık 42 görür
** subrange’ın ömrü, üzerine baktığı container’ın ömrüne bağlıdır
(container ölürse view’daki iteratörler “dangling” olur)
***************************** NOT : Bazı view’lar owning olabilir (örn. std::ranges::owning_view), ama “çoğu view” gerçekten non-owning’dir.
std::ranges::subrange “iterator + sentinel” çiftiyle kurulabilir ve begin ile end’in aynı tür olması şart değildir.
template <int T>
struct Sentinel {
// iterator == sentinel
template <typename It>
friend bool operator==(const It& it, Sentinel) {
return *it == T;
}
// sentinel == iterator (simetri)
template <typename It>
friend bool operator==(Sentinel s, const It& it) {
return it == s;
}
};
int main()
{
std::vector<int> ivec{ 1, 3, 5, 7, 2, 9 };
std::ranges::subrange sb(ivec.begin(), Sentinel<7>{});
for (int v : sb)
{
std::cout << v << ' ';
}
}
*****************************
AÇIKLAMA : subrange için “end” illa iterator değildir
C++20 range kullanımında “bir range” şu ikiliyle tanımlanır:
1. begin(r) → iterator
2. end(r) → sentinel
-> Klasik STL container’larda çoğunlukla end() de iterator’dır (yani begin/end aynı tip).
Ama ranges tasarımı şunu bilinçli olarak ayırır:
** Iterator: elemanlara erişip ilerleyen şey
** Sentinel: burada dur koşulunu temsil eden, iterator olmak zorunda olmayan bitiş işareti
-------------------------------------------------------------------
-> Bu, özellikle şu durumlarda avantaj sağlar:
** Sonu bir sayıya/karaktere gelince biten akışlar (örn. '\n' görünce dur)
** Null-terminated diziler / C-string benzeri yapılar
** Dosya/stream iterator’larında “EOF sentinel”
** Performans/yer optimizasyonu: end iterator’ı taşımaya gerek kalmaz
*****************************
CEVAP : 1 3 5
*****************************template <auto T>
struct Sentinel {
template <class It>
friend bool operator==(const It& it, Sentinel)
{
return *it == T;
}
template <class It>
friend bool operator==(Sentinel s, const It& it)
{
return it == s;
}
};
void print(std::ranges::range auto&& r)
{
for (const auto& val : r)
{
std::cout << val << " ";
}
std::cout << "\n";
}
int main()
{
auto vw = std::views::iota(0, 20);
std::vector<int> vec(vw.begin(), vw.end());
std::ranges::subrange s1{ vec.begin(), vec.begin() + 7 };
/* “*it == 5 olunca bitir” anlamına gelir ve
print(s2) teorik olarak 0 1 2 3 4 basar*/
std::ranges::subrange s2{ vec.begin(), Sentinel<5>{} };
print(s1); // 0 1 2 3 4 5 6
print(s2); // 0 1 2 3 4
}template <auto T>
struct Sentinel {
template <class It>
friend bool operator==(const It& it, Sentinel)
{
return *it == T;
}
template <class It>
friend bool operator==(Sentinel s, const It& it)
{
return it == s;
}
};
// bu bir template, constraint template parameter. range bir concept
void print(std::ranges::range auto&& r)
{
for (const auto& val : r)
{
cout << val << " ";
}
cout << "\n";
}
int main()
{
auto vec = ranges::iota_view{ 0, 10 } | ranges::to<vector>();
// Bu, vektörün ilk 7 elemanına bakan bir “pencere” oluşturur:
ranges::subrange s1{ vec.begin(), vec.begin() + 7 };
// Burada end bir iterator değil, “bitiş koşulu” sağlayan bir sentinel.
ranges::subrange s2{ vec.begin(), Sentinel<5>{} };
print(s1);
print(s2);
}
*****************************
AÇIKLAMA :
-> ranges::to<vector>() C++23 ile geldi.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1206r7.pdf
--------------------------------------------------------------
C++23: ranges::to<vector>() ile view → container dönüşümü yapar.
- auto vec = ranges::iota_view{ 0, 10 } | ranges::to<vector>(); -
1. iota_view{0,10} lazily (tembel) olarak 0 1 2 3 4 5 6 7 8 9 üretir.
2. ranges::to<vector>() bu view’ı gezip elemanları gerçek bir std::vector<int> içine materialize eder.
3. Sonuç: vec == {0,1,2,3,4,5,6,7,8,9}
***************************** subrange sınıfı türünden bir nesneyi oluştururken
3.parametresine arguman geçilerek onun sized_range olması sağlanabilir.
int main()
{
namespace rng = std::ranges;
static_assert(rng::sized_range<vector<int>>);
}
*****************************
AÇIKLAMA : vector<int> açılımı sized_range
***************************** int main()
{
namespace rng = std::ranges;
static_assert(rng::sized_range<forward_list<int>>);
}
*****************************
AÇIKLAMA : forward_list<int> açılımı sized_range değil
forward_list c++11 ile eklenen container ve size yoktur. constant time olmadığı içindir. burda distance kullanılır ve o da kendi içinde döngü oluşturur.
*****************************
CEVAP : syntax error
*****************************int main()
{
std::vector ivec{ 2, 3, 5, 7, 11, 13 };
std::ranges::subrange sbvec{ next(ivec.begin()), prev(ivec.end()) };
constexpr bool b1 = std::ranges::sized_range<decltype(sbvec)>; // true
std::cout << "b1 : " << b1 << "\n";
list ilist{ 2, 3, 5, 7, 11, 13 };
std::ranges::subrange sblist{ ilist.begin(), ilist.end(), ilist.size() };
// elle sized_range yaptık
constexpr bool b2 = std::ranges::sized_range<decltype(sblist)>; // true
std::cout << "b2 : " << b2 << "\n";
}
*****************************
AÇIKLAMA :
-> std::ranges::subrange normalde iki iterator/sentinel tutar: (begin, end).
-> Eğer subrange boyutu (size) “ucuz/kolay” şekilde hesaplayabiliyorsa, std::ranges::sized_range olur ve std::ranges::size(sb) O(1) çalışır.
-> Vector örneği (sbvec): vector iteratörleri random-access olduğu için end - begin ile aralık boyu kolayca bulunur. Bu yüzden sbvec zaten sized_range çıkar (b1 == true).
-> List örneği (sblist): std::list iteratörü random-access değildir; (end - begin) yoktur. Dolayısıyla subrange{begin,end} tek başına sized olmazdı.
** Ancak subrange’ın 3. parametresi size bilgisini “dışarıdan” vermene izin verir: subrange{ begin, end, size }
** Bu sayede sblist “store size” modunda tutulur ve sized_range olur (b2 == true).
-> Yani “3. argümanla sized_range yapma” özellikle non-random-access iteratörlü range’lerde pratik bir tekniktir.
*****************************
CEVAP :
b1 : 1
b2 : 1
*****************************int main()
{
int ar1[10]{};
int ar2[20]{};
using sbr_type1 = decltype(std::ranges::subrange{ar1.begin(), ar1.end()});
using sbr_type2 = decltype(std::ranges::subrange{ar2.begin(), ar2.end()});
static_assert(std::same_as<sbr_type1, sbr_type2>);
}
*****************************
AÇIKLAMA :
-> Burada subrange’ı iterator + iterator ile kurduk.
-> ar1.begin() ifadesi gerçekte çoğu zaman int* gibi davranır; ar2.begin() de int* gibi davranır.
-> Ama kritik fark: bu şekilde yazıldığında CTAD/ctor seçimi “sized” bilgisi, sentinel türü, internal subrange_kind seçimi gibi detaylara göre farklı tür çıkarabilir (özellikle store-size seçimi ve deduction rehberleri).
-> Bu örnek “aynı element türünde olsa bile, subrange’ı hangi ctor/deduction yoluyla kurduğuna bağlı olarak tür eşitliği garanti değildir” mesajını verir.
*****************************
CEVAP : false
*****************************int main()
{
int ar1[10]{};
int ar2[20]{};
using sbr_type1 = decltype(std::ranges::subrange{ ar1 });
using sbr_type2 = decltype(std::ranges::subrange{ ar2 });
static_assert(std::same_as<sbr_type1, sbr_type2>);
}
*****************************
AÇIKLAMA :
-> Burada subrange’a doğrudan range objesi veriyorsun (array bir range’tir).
-> CTAD bu durumda daha standart/tek bir deduction yolu izler:
** Begin iterator tipi: int*
** Sentinel tipi: int* (array için end de pointer)
** subrange_kind çoğu implementasyonda “sized” seçilir (array boyutu biliniyor).
-> Array boyutunun (10 vs 20) farklı olması subrange’ın tip parametrelerine girmez; çünkü tip parametreleri iterator/sentinel türlerinden oluşur.
** Her ikisi de int* olduğundan türler aynıdır.
*****************************
CEVAP : true
*****************************template <std::ranges::random_access_range Range>
auto left_half(Range& r)
{
auto first = std::ranges::begin(r);
auto mid = first + std::ranges::size(r) / 2;
return std::ranges::subrange(first, mid);
}
template <std::ranges::random_access_range Range>
auto right_half(Range& r)
{
auto first = std::ranges::begin(r);
auto mid = first + std::ranges::size(r) / 2;
auto last = std::ranges::end(r);
return std::ranges::subrange(mid, last);
}
int main()
{
std::vector vec{ 1, 2, 3, 4, 5, 7, 9, 65, 45, 25 };
// C++23'te ranges formatting + std::format desteği varsa:
std::cout << std::format("{} {}\n", left_half(vec), right_half(vec));
}
*****************************
AÇIKLAMA :
-> left_half / right_half fonksiyonları bir random-access range’i iki parçaya bölüp iki ayrı subrange döndürür.
-> random_access_range constraint’i şu yüzden gerekli:
** first + n gibi iterator aritmetiği gerekiyor.
** std::ranges::size(r) O(1) olmalı (çoğu random-access range bunu sağlar).
-> lifetime önemli: subrange elemanları kopyalamaz; sadece iterator tutar.
** Eğer Range r değerle alınırsa r fonksiyon içinde kopyalanır, fonksiyon bitince yok olur, dönen subrange “dangling iterator” taşır → UB.
** Bu yüzden parametreyi Range& (veya daha genel Range&& + forwarding) almak gerekir.
*****************************
CEVAP : [1, 2, 3, 4, 5] [7, 9, 65, 45, 25]
*****************************default_sentinel
struct default_sentinel_t {}; (1) (since C++20)
inline constexpr default_sentinel_t default_sentinel{}; (2) (since C++20)1) default_sentinel_t is an empty class type used to denote the end of a range. It can be used together with iterator types that know the bound of their range (e.g., std::counted_iterator).
2) default_sentinel is a constant of type default_sentinel_t.
int main()
{
std::vector<int> ivec{3, 6, 8, 9, 1, 7};
for (std::counted_iterator it{ivec.begin(), 3}; it != std::default_sentinel; ++it)
{
std::cout << *it << ' ';
}
}
*****************************
AÇIKLAMA : sentinel ile
*****************************
CEVAP : 3 6 8
*****************************#include <print>
#include <regex>
#include <string>
int main()
{
const std::string s = "Quick brown fox.";
const std::regex words_regex("[^\\s]+");
const std::ranges::subrange words(
std::sregex_iterator(s.begin(), s.end(), words_regex), std::default_sentinel);
std::println("Found {} words:", std::ranges::distance(words));
for (const std::smatch& match : words)
{
std::println("{}", match.str());
}
}
*****************************
AÇIKLAMA : cpp reference
*****************************
CEVAP :
Found 3 words:
Quick
brown
fox.
*****************************std::ranges::for_each, std::ranges::for_each_result
// Call signature
template <std::input_iterator I, std::sentinel_for<I> S, class Proj = std::identity,
std::indirectly_unary_invocable<std::projected<I, Proj>> Fun>
constexpr for_each_result<I, Fun>
for_each(I first, S last, Fun f, Proj proj = {});
template <ranges::input_range R, class Proj = std::identity,
std::indirectly_unary_invocable<std::projected<ranges::iterator_t<R>, Proj>> Fun>
constexpr for_each_result<ranges::borrowed_iterator_t<R>, Fun>
for_each( R&& r, Fun f, Proj proj = {} );
// Helper types
template <class I, class F>
using for_each_result = ranges::in_fun_result<I, F>;struct NT {
friend bool operator==(const char* p, NT) { return *p == '\0'; }
friend bool operator==(NT s, const char* p) { return p == s; }
friend bool operator!=(const char* p, NT s) { return !(p == s); }
friend bool operator!=(NT s, const char* p) { return !(p == s); }
};
int main()
{
char arr[] = "smart code";
// iterator-sentinel overload: (I first, S last, F f [, Proj])
std::ranges::for_each(arr, NT{}, [](char chr)
{
std::cout << "(" << chr << ")";
});
}
*****************************
CEVAP : (s)(m)(a)(r)(t)( )(c)(o)(d)(e)
*****************************std::ranges::find, std::ranges::find_if, std::ranges::find_if_not
Call signature
(1)
template< std::input_iterator I, std::sentinel_for<I> S,
class T, class Proj = std::identity >
requires std::indirect_binary_predicate
<ranges::equal_to, std::projected<I, Proj>, const T*>
constexpr I find( I first, S last, const T& value, Proj proj = {} );
(since C++20)
(until C++26)
template< std::input_iterator I, std::sentinel_for<I> S,
class Proj = std::identity,
class T = std::projected_value_t<I, Proj> >
requires std::indirect_binary_predicate
<ranges::equal_to, std::projected<I, Proj>, const T*>
constexpr I find( I first, S last, const T& value, Proj proj = {} );
(since C++26)
(2)
template< ranges::input_range R, class T, class Proj = std::identity >
requires std::indirect_binary_predicate
<ranges::equal_to,
std::projected<ranges::iterator_t<R>, Proj>, const T*>
constexpr ranges::borrowed_iterator_t<R>
find( R&& r, const T& value, Proj proj = {} );
(since C++20)
(until C++26)
template< ranges::input_range R, class Proj = std::identity,
class T = std::projected_value_t<ranges::iterator_t<R>, Proj> >
requires std::indirect_binary_predicate
<ranges::equal_to,
std::projected<ranges::iterator_t<R>, Proj>, const T*>
constexpr ranges::borrowed_iterator_t<R>
find( R&& r, const T& value, Proj proj = {} );
(since C++26)
template< std::input_iterator I, std::sentinel_for<I> S,
class Proj = std::identity,
std::indirect_unary_predicate<std::projected<I, Proj>> Pred >
constexpr I find_if( I first, S last, Pred pred, Proj proj = {} );
(3) (since C++20)
template< ranges::input_range R, class Proj = std::identity,
std::indirect_unary_predicate
<std::projected<ranges::iterator_t<R>, Proj>> Pred >
constexpr ranges::borrowed_iterator_t<R>
find_if( R&& r, Pred pred, Proj proj = {} );
(4) (since C++20)
template< std::input_iterator I, std::sentinel_for<I> S,
class Proj = std::identity,
std::indirect_unary_predicate<std::projected<I, Proj>> Pred >
constexpr I find_if_not( I first, S last, Pred pred, Proj proj = {} );
(5) (since C++20)
template< ranges::input_range R, class Proj = std::identity,
std::indirect_unary_predicate
<std::projected<ranges::iterator_t<R>, Proj>> Pred >
constexpr ranges::borrowed_iterator_t<R>
find_if_not( R&& r, Pred pred, Proj proj = {} );template <auto T> // non type paramater
struct EndSent {
bool operator==(auto pos) const
{
return *pos == T;
}
};
int main()
{
std::vector<int> ivec{ 1, 5, 7, 9, 2, 3, 6, 79, 90 };
auto iter = std::ranges::find(ivec.begin(), EndSent<3>{}, 3);
std::cout << *iter << "\n";
}
*****************************
CEVAP : 3
*****************************std::ranges::sort
Call signature
template< std::random_access_iterator I, std::sentinel_for<I> S,
class Comp = ranges::less, class Proj = std::identity >
requires std::sortable<I, Comp, Proj>
constexpr I sort( I first, S last, Comp comp = {}, Proj proj = {} );
template< ranges::random_access_range R, class Comp = ranges::less,
class Proj = std::identity >
requires std::sortable<ranges::iterator_t<R>, Comp, Proj>
constexpr ranges::borrowed_iterator_t<R>
sort( R&& r, Comp comp = {}, Proj proj = {} );template <auto T> // non type paramater
struct EndSent {
bool operator==(auto pos) const
{
return *pos == T;
}
};
int main()
{
std::vector<int> ivec{ 1, 5, 7, 9, 2, 3, 6, 79, 90 };
std::ranges::sort(ivec.begin(), EndSent<3>{});
for (auto val : ivec)
{
std::cout << val << " ";
}
}
*****************************
AÇIKLAMA : 3 konumuna kadar sort eder.
*****************************
CEVAP : 1 2 5 7 9 3 6 79 90
*****************************std::unreachable_sentinel_t, std::unreachable_sentinel
Tek başına kullanılırsa döngü bitmez → her zaman take(n) / take_while(pred) gibi sınırlandırıcı ile kullanılmalıdır.
Pratikte çoğu zaman kendin yazmazsın; iota gibi “sınırsız” view’lar içeride bunu kullanır.
Dizi/konteynerin “end”i olarak vermek yanlıştır (UB).
struct unreachable_sentinel_t;
inline constexpr unreachable_sentinel_t unreachable_sentinel{};struct UnreachableSentinel_t {
constexpr bool operator==(const auto& pos) const
{
return false;
}
};
constexpr UnreachableSentinel_t UnreachableSentinel;
*****************************
AÇIKLAMA :
Fonksiyonu çağırdığımızda sürekli false döndürür. Derleyici false döndürdüğünü gördüğü için hiç çağırmayabilir.
Yani ortada bir koşul kalmaz. Bu türden bir nesne end iteratorunun konumu yapılırsa fiilen bir end ile karşılaştırma olmaz.
***************************** #include <ranges>
#include <iostream>
int main()
{
auto val = std::views::iota(0) // 0,1,2,3,... (içeride unreachable_sentinel)
| std::views::take(8); // ilk 8 vuruş
for (int sc : val)
std::cout << sc << ' '; // 0..7
}int main()
{
int arr[]{1,2,3};
auto bad_val = std::ranges::subrange{arr, std::unreachable_sentinel}; // kullanma !
// for (int sc : bad_val) ... // tehlikeli kullanım olur
}std::ranges::shuffle
Container’ın kendisini verebiliriz.
Geriye aynı range referansını döndürür → chaining mümkün.
Concepts ile kısıtlıdır: sadece random_access_range + sortable.
Daha okunaklı ve güvenlidir, çünkü yanlış iterator eşleşmelerini engeller.
template <std::random_access_iterator I, std::sentinel_for<I> S, class Gen>
requires std::permutable<I> && std::uniform_random_bit_generator<std::remove_reference_t<Gen>>
I shuffle(I first, S last, Gen&& gen);
template <ranges::random_access_range R, class Gen>
requires std::permutable<ranges::iterator_t<R>> && std::uniform_random_bit_generator<std::remove_reference_t<Gen>>
ranges::borrowed_iterator_t<R> shuffle( R&& r, Gen&& gen );std::shuffle ile
std::shuffle(vec.begin(), vec.end(), eng);std::ranges::shuffle
auto rng = std::views::iota(0, 10) | std::views::transform([](int x){ return x*x; });
std::vector<int> ivec(rng.begin(), rng.end());
std::ranges::shuffle(ivec, eng); // doğrudan container geçilirint main()
{
std::vector<int> ivec(100);
// 0..99 ile doldur
std::iota(ivec.begin(), ivec.end(), 0);
// karıştır
std::mt19937 eng{ std::random_device{}() };
std::ranges::shuffle(ivec, eng);
int val{};
std::cout << "aranacak deger : ";
std::cin >> val;
if (auto it = std::ranges::find(ivec, val); it != ivec.end())
{
std::cout << std::ranges::distance(ivec.begin(), it)
<< " indexli eleman olarak bulundu\n";
}
else
{
std::cout << "bulunamadi\n";
}
}
*****************************
AÇIKLAMA : *it değerinin val değerine eşitliği sınandı.
*****************************
CEVAP :
*****************************rangified algoritmaların geri dönüş değeri türleri farklıdır.
Parametre:
Eski algoritmalar iterator çifti alır (first, last).
Ranges algoritmaları doğrudan range (container, view) alabilir.
Geri dönüş türü:
Eski algoritmalar genellikle yalnızca iterator döndürür.
Ranges algoritmaları genelde bir result struct döndürür.Bu struct’ın içinde:
in → giriş iteratoru (işlem sonunda nereye gelindiğini gösterir).
out → çıktı iteratoru (kopyalama/tasarlama algoritmalarında).
fun → kullanılan fonksiyon veya predikat (örn. for_each’te).
Amaç :
Daha fazla bilgi geri döndürmek (hem iterator konumu hem de kullanılan fonksiyon objesi).
constexpr uyumlu ve güvenli hale getirmek.
Zincirleme (pipeline) kullanımlarda daha esnek olmak.
int main()
{
vector<int> ivec{ 3, 6, 7, 9, 1 };
auto res = ranges::for_each(vec, [](int x){ cout << x; });
}
*****************************
AÇIKLAMA :
res.fun --> functor
.in --> return değeri bu range deki iterator
***************************** Kısacası, std::ranges::for_each’in dönüş türü:
template<class I, class F>
struct for_each_result {
I in; // son ulaşılan iterator
F fun; // kullanılan fonksiyon objesi
};
Bu sayede aynı fonksiyon objesini tekrar kullanabilir, veya algoritmanın tam olarak nerede durduğunu görebiliriz.rangified algoritmaların geri dönüş tipi artık genellikle bir resultstruct’ıdır. Bu struct içinde algoritmanın en son dokunduğu yerden sonrası bilgisi saklanır.
Genel Kural
Klasik STL algoritmaları (std::find, std::for_each, std::copy) → genelde tek bir iterator döner. Bu iterator, algoritmanın işini bitirdiği “son nokta”yı gösterir.
C++20 std::ranges sürümleri → genellikle bir struct döndürür. Bu struct:
in → giriş tarafında en son kullanılan iterator (“en son yazdığı/okuduğu yerin sonrasını” işaret eder).
out → çıktı tarafında en son yazılan konum (yazma yapan algoritmalarda).
fun → kullanılan fonksiyon veya predikat (predicate/functor) (for_each, remove_if, vs. gibi).
int main()
{
vector<int> vec1{ 3, 6, 7, 9, 1 };
vector<int> vec2(20);
auto iter = copy(vec1.begin(), vec1.end(), vec2.begin());
// vec2 de kopyalanmış öğelerin range'ini kullanmak istersek range şu şekilde ifade etmemiz gerekir.
// vec2.begin() + iter
// copy yazdığı konumdan sonraki konumu döndürdü. yazma algoritmalarının hepsi bu şekildedir.
}template<typename T>
void print(T beg, T end, const char* p = " ", std::ostream& os = std::cout)
{
while (beg != end)
{
os << *beg++ << p;
}
os << p;
}
int main()
{
std::vector<int> vec1{ 3, 6, 7, 9, 1 };
std::vector<int> vec2(10);
// geri dönüş değeri en son 1'i yazdığı yerden sonraki konum
auto iter = std::copy(vec1.begin(), vec1.end(), vec2.begin());
print(vec2.begin(), vec2.end()); // 3 6 7 9 1 0 0 0 0 0
print(vec2.begin(), iter); // 3 6 7 9 1
}
*****************************
AÇIKLAMA :
*****************************
CEVAP : 3 6 7 9 1 0 0 0 0 0 3 6 7 9 1
*****************************int main()
{
std::vector vec1{3, 6, 7, 9, 1};
std::vector<int> vec2(10);
auto res = std::ranges::copy(vec1, vec2.begin());
// res: std::ranges::copy_result<In, Out> (aggregate)
// res.in → kaynakta okumanın bittiği konum (genelde ranges::end(vec1))
// res.out → hedefte yazmanın bittiği konum (std::copy'nin döndürdüğüne denk)
print(vec2.begin(), res.out); // sadece kopyalanan kısmı yaz: 3 6 7 9 1
print(res.out, vec2.end()); // kalan kısım: 0 0 0 0 0
}
*****************************
AÇIKLAMA :
Rangified algoritmalar, tek iterator yerine yapı döndürür (ör. copy_result{in, out}).
yani; “nerede okumayı bıraktım?” + “nerede yazmayı bıraktım?” bilgisini bir arada verir.
Ek olarak std::ranges::copy konseptlerle kısıtlıdır: kaynak input_range, hedef indirectly_copyable olmalı; yanlış kullanım derlemede elenir.
*****************************
CEVAP : 3 6 7 9 1 0 0 0 0 0
*****************************template<typename T>
void print(T beg, T end, std::string_view sw = " ", std::ostream& os = std::cout)
{
if (beg == end)
{
os << '\n';
return;
}
os << *beg; ++beg;
while (beg != end)
{
os << sw << *beg;
++beg;
}
os << '\n';
}
int main()
{
std::vector vec1{ 3, 6, 7, 9, 1 };
std::vector<int> vec2(10);
auto [src, dst] = std::ranges::copy(vec1, vec2.begin());
// target
print(vec2.begin(), vec2.end()); // 3 6 7 9 1 0 0 0 0 0
// Sadece kopyalanan bölüm:
print(vec2.begin(), dst); // 3 6 7 9 1
// kaynağın nerede bittiğini de kontrol
if (src == std::ranges::end(vec1))
{
std::cout << "Kaynak tamamen kopyalandi.\n";
}
}
*****************************
AÇIKLAMA : structured binding ile de kullanılabilir.
*****************************
CEVAP :
3 6 7 9 1 0 0 0 0 0
3 6 7 9 1
Kaynak tamamen kopyalandi.
*****************************projection
projection, elemandan özellik/transform üretip onunla val’i karşılaştırır.
Algoritmaya verilen öğeyi önce bir “projeksiyon fonksiyonundan” geçirip öyle karşılaştırmak / sıralamak / aramak demektir.
C++20 ile gelen Ranges kütüphanesinde bütün sıralama, arama, karşılaştırma fonksiyonları projeksiyon parametresini destekler.
std::sort(v.begin(), v.end(),
[](const Person& a, const Person& b){
return a.age < b.age;
});
*****************************
AÇIKLAMA : c++17 ve öncesi
***************************** std::ranges::sort(v, {}, &Person::age);
/******************************************
AÇIKLAMA : c++20 range ile
Buradaki üçüncü parametre olan {} comparator ve &Person::age ise projection.
Person → age
: projeksiyonu kullanıldı.
******************************************/template <class T, typename U> T
find(T beg, T end, const U& val)
{
while (beg != end)
{
// *beg ile iterator dereference edilir ve o konumdaki nesneye erişilir.
if (*beg == val)
{
return beg;
}
++beg;
}
return end;
}
int main()
{
vector<int> vec{ 3, 6, 7, 9, 1 };
find(vec.begin(), vec.end(), 6);
}
*****************************
AÇIKLAMA :
STL find algoritması C++20 öncesi verilen range içinde bir key aranır, bulunursa o konum bulunamazsa end konumu döndürülür.
***************************** #include <vector>
#include <iostream>
#include <ranges>
// projection'lı klasik find
template <class It, class V, class Proj>
It find(It beg, It end, const V& val, Proj pr)
{
while (beg != end)
{
if (pr(*beg) == val)
{
return beg;
}
++beg;
}
return end;
}
int main()
{
std::vector<int> vec{ 3, 6, 7, 9, 1 };
if (auto it = find(vec.begin(), vec.end(), 36, [](int x){ return x * x; }); it != vec.end())
{
std::cout << "bulundu: " << *it << "\n";
}
// C++20 ranges sürümü (projeksiyon parametresiyle):
if (auto it2 = std::ranges::find(vec, 36, [](int x){ return x * x; }); it2 != vec.end())
{
std::cout << "ranges::find ile: " << *it2 << "\n";
}
// Struct alanına göre arama (projeksiyon = &str_name::id)
struct str_name {
int id;
std::string name;
};
std::vector<str_name> svec{{2,"b"},{1,"a"}};
if (auto it3 = std::ranges::find(svec, 1, &str_name::id); it3 != svec.end())
{
std::cout << "name : " << it3->name << "\n";
}
}std::invoke (HATIRLATMA!) https://ysfhncr.wixsite.com/smartcoding/post/c-dilinde-std-invoke
template <class T, typename U> T
find(T beg, T end, const U& val)
{
while (beg != end)
{
//*beg ile iterator dereference edilir ve o konumdaki nesneye erişilir.
if (*beg == val)
{
return beg;
}
++beg;
}
return end;
}
int main()
{
std::vector<int> vec{ 3, 6, 7, 9, 1 };
std::find(vec.begin(), vec.end(), 6);
}
*****************************
AÇIKLAMA :
STL find algoritması C++20 öncesi verilen range içinde bir key aranır, bulunursa o konum bulunamazsa end konumu döndürülür.
***************************** template <class T, typename U, typename P>
T find(T beg, T end, const U& val, P pr)
{
while (beg != end)
{
if (std::invoke(pr, *beg) == val)
{
return beg;
}
++beg;
}
return end;
}
*****************************
AÇIKLAMA : artık 3. template parametresi hem member function pointer hem de data member function olabilecek.
yani hem bir callable function hem de data member adresi geçebiliriz.
***************************** #include <vector>
#include <iostream>
#include <ranges>
// projection'lı klasik find
template <class It, class V, class Proj>
It find(It beg, It end, const V& val, Proj pr)
{
while (beg != end)
{
if (pr(*beg) == val)
{
return beg;
}
++beg;
}
return end;
}
int main()
{
std::vector<int> vec{ 3, 6, 7, 9, 1 };
if (auto it = find(vec.begin(), vec.end(), 36, [](int x){ return x * x; }); it != vec.end())
{
std::cout << "bulundu: " << *it << "\n";
}
// C++20 ranges sürümü (projeksiyon parametresiyle):
if (auto it2 = std::ranges::find(vec, 36, [](int x){ return x * x; }); it2 != vec.end())
{
std::cout << "ranges::find ile: " << *it2 << "\n";
}
// Struct alanına göre arama (projeksiyon = &str_name::id)
struct str_name {
int id;
std::string name;
};
std::vector<str_name> svec{ { 2, "b" },{ 1, "a" } };
if (auto it3 = std::ranges::find(svec, 1, &str_name::id); it3 != svec.end())
{
std::cout << "name : " << it3->name << "\n";
}
}İlk olarak dereference edilen iterator nesnesini kullanmak yerine onu yeni bir template parametresi kullanarak adeta transform ederek kullandı.(pr(*beg))
Bu durumda bir transformation istenmiyorsa, yani find algoritmasını ilk haliyle kullanamayız illaki bir projection ile kullanmak gerekir.
Bunun çözümü için 3.template parametresine default argument geçeriz.
std::identity (c++20)
bunun operator fonksiyonuna ne geçilirse geçilsin aynı değeri return eder.
template <typename T, typename U, typename P>
T find(T beg, T end, const U& val, P pr = std::identity)
{
while (beg != end)
{
if (std::invoke(pr, *beg) == val)
{
return beg;
}
++beg;
}
return end;
}
*****************************
AÇIKLAMA :
Bu sürümde üçüncü template parametresi P, std::invoke sayesinde çok esnektir:
- normal bir callable (lambda, fonksiyon nesnesi),
- fonksiyon pointer’ı,
- member function pointer,
- data member pointer olarak kullanılabilir.
Yani pr parametresine ister bir fonksiyon/lambda, ister bir üye adresi (&T::member) geçebiliriz; std::invoke(pr, *beg) bunların hepsini doğru şekilde çağırabilir.
Ancak burada P için template seviyesinde bir varsayılan değer verilmediği için, find(v.begin(), v.end(), 42) gibi bir çağrıda P deduct edilmez; pratikte 4. argümanı atayamayız. Bu yüzden bu imza eksik kalıyor.
Not: P pr = std::identity ifadesi de tip olarak doğru değil; std::identity bir tür, değer değildir. Aslında P pr = {}; veya std::identity pr{}; gibi kullanılmalıdır.
***************************** #include <functional> // std::invoke, std::identity
template <typename T, typename U, typename P = std::identity>
T find(T beg, T end, const U& val, P pr = {})
{
while (beg != end)
{
if (std::invoke(pr, *beg) == val)
{
return beg;
}
++beg;
}
return end;
}
*****************************
AÇIKLAMA :
Üçüncü template parametresi P için std::identity varsayılan tür olarak atanmıştır:
template <typename T, typename U, typename P = std::identity>
Yani çağrıda P açıkça belirtilmezse, P otomatik olarak std::identity olur.
Dördüncü fonksiyon parametresi pr için de varsayılan değer {} verilmiştir:
P pr = {};
Bu, P’nin (yani varsayılanda std::identity’nin) default-construct edilmesi demektir: pr fiilen std::identity{} olur.
Dolayısıyla, find(v.begin(), v.end(), 42) çağrısında:
- P → std::identity,
- pr → std::identity{} dedüklenir.
- std::invoke(pr, *beg) ifadesi, identity(*beg) ≡ *beg olduğu için doğrudan *beg kullanılmış olur. Yani std::invoke(pr, *beg) == val ifadesi, fiilen *beg == val’dir.
Eğer istenirse P’ye:
- bir lambda / fonksiyon nesnesi,
- bir serbest fonksiyon pointer’ı,
- bir member function pointer (&T::foo),
- bir data member pointer (&T::member)
da verilebilir; std::invoke bunların hepsini doğru şekilde çağırabildiği için, P proje fonksiyonu/projection olarak esnekçe kullanılabilir.
***************************** #include <algorithm>
#include <functional>
#include <iostream>
#include <random>
#include <ranges>
#include <vector>
struct Irand {
std::mt19937 eng{std::random_device{}()};
std::uniform_int_distribution<int> dist;
Irand(int min, int max) : dist(min, max) {}
int operator()()
{
return dist(eng);
}
};
template <std::ranges::input_range R>
void print(const R& r)
{
for (const auto& x : r)
{
std::cout << x << ' ';
}
std::cout << '\n';
}
int main()
{
std::vector<int> vec;
vec.reserve(20);
Irand gen{-50, 50};
std::generate_n(std::back_inserter(vec), 20, gen);
print(vec);
std::ranges::sort(vec);
print(vec);
}
*****************************
AÇIKLAMA :
std::ranges::sort(vec); çağrısında:
- Karşılaştırıcı (comparator) varsayılan olarak std::ranges::less kullanılır.
- Projeksiyon (projection) varsayılan olarak std::identity kullanılır.
sort(vec) dediğimizde:
- Elemanlar std::identity sayesinde olduğu gibi (*it) projekte edilir,
- Bu değerler std::ranges::less{} ile karşılaştırılır,
- Sonuç: vec kendi eleman değerlerine göre artan sırada sıralanır.
***************************** #include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <random>
#include <ranges>
#include <vector>
struct Irand {
std::mt19937 eng{ std::random_device{}() };
std::uniform_int_distribution<int> dist;
Irand(int min, int max) : dist(min, max) {}
int operator()() { return dist(eng); }
};
struct Point {
Point() = default;
Point(int x, int y) : mx{ x }, my{ y } {}
friend std::ostream& operator<<(std::ostream& os, const Point& p)
{
return os << "[" << p.mx << ", " << p.my << "]";
}
int mx{}, my{};
};
Point create_random_point()
{
Irand rand{ 0, 99 };
return Point{ rand(), rand() };
}
int main_simple()
{
for (int i{}; i < 10; ++i)
{
cout << create_random_point() << "\n";
}
}
*****************************
AÇIKLAMA : operator<< sayesinde "[x, y]" formatında yazdırılıyor.
***************************** int main()
{
vector<Point> pvec(20); // 20 elemanlı vektör (default-constructed)
// pvec içindeki her elemana create_random_point() sonucu atanır
std::ranges::generate(pvec, create_random_point);
// pvec içeriğini satır satır yazdır
std::ranges::copy(
pvec,
std::ostream_iterator<Point>(cout, "\n")
);
}
*****************************
AÇIKLAMA :
-> ranges::copy imzası
template <std::ranges::input_range R, std::weakly_incrementable O>
auto copy(R&& r, O result);
- Range: pvec,
- Output iterator: std::ostream_iterator<Point>(cout, "\n").
- Etki: pvec’in elemanları sırayla cout’a, \n son ek karakteriyle yazdırılır.
***************************** int main()
{
vector<Point> pvec(20);
ranges::generate(pvec, create_random_point);
ranges::sort(pvec); // syntax error
}
*****************************
AÇIKLAMA : özetle karşılaştırmayı yapacak < operatoru yoktur.
-> std::ranges::sort imzası
template<
std::ranges::random_access_range R,
class Comp = std::ranges::less,
class Proj = std::identity
>
requires std::sortable<std::ranges::iterator_t<R>, Comp, Proj>
constexpr void sort(R&& r, Comp comp = {}, Proj proj = {});
------------------------------
Biz ranges::sort(pvec); dediğimizde:
-> Comp = std::ranges::less
-> Proj = std::identity
karşılaştırma : less(identity(a), identity(b)) // yani less(a, b)
-> less(a, b) ifadesi için a ve b’nin türü Point.
Point için:
-> ne operator< tanımlı,
-> ne de <=> (strong ordering vb.) tanımlı.
-------------------------------
-> Dolayısıyla Point sortable değil, ranges::sort’un requires şartı sağlanmıyor → derleme hatası.
***************************** int main()
{
vector<Point> pvec(20);
ranges::generate(pvec, create_random_point);
ranges::sort(pvec, {}, &Point::mx);
ranges::copy(pvec, ostream_iterator<Point>(cout, "\n"));
}
*****************************
AÇIKLAMA :
- Point sınıfının mx değişkeninin adresini ranges::sort algoritmasının projection parametresine geçtik.
- sort algoritması iter konumundaki öğeleri kullanırken arka planda std::invoke çağırıldı ve mx i döndürdü.
- artık iter konumundaki nesneleri değil iter konumundaki nesnelerin mx'lerini karşılaştırdık ve sıralama da mx lere göre oldu.
---------------------------------------------------------------
- ranges::sort(pvec, {}, &Point::mx); çağrısında:
->ranges::sort(pvec, {}, &Point::mx); çağrısında:
->Üçüncü argüman &Point::mx → projection: int Point::* (data member pointer).
- sort her karşılaştırmada konsept olarak şunu yapar:
auto a_val = std::invoke(&Point::mx, a); // a.mx
auto b_val = std::invoke(&Point::mx, b); // b.mx
if (less(a_val, b_val)) { ... }
---------------------------------------------------------------
*it doğrudan karşılaştırılmıyor,
önce std::invoke(proj, *it) ile mx’e projekte ediliyor,
sonra bu int değerler less ile karşılaştırılıyor.
***************************** int main()
{
vector<string> svec{
"Mercury",
"Venus",
"Earth",
"Mars",
"Jupiter",
"Saturn"
};
// İçeriği yazdırma
ranges::copy(svec, ostream_iterator<string>(cout, "\n"));
// derleme HATASI:
ranges::find(svec, 15); // 15 int, svec elemanı string
}
*****************************
AÇIKLAMA : ranges::find(svec, 15) çağrısı derleme zamanında constraint violation hatası verir.
std::ranges::find algoritmas
-------------------------------------------
template<ranges::input_range R, class T, class Proj = identity>
requires indirect_binary_predicate<ranges::equal_to, projected<R::iterator, Proj>, const T*>
constexpr ranges::borrowed_iterator_t<R>
find(R&& r, const T& value, Proj proj = {});
-------------------------------------------
requires ve ranges::equal_to icin :
-> proj(*it) (range elemanı → string)
-> value (bizde → int, yani 15)
türlerini eşitlik açısından karşılaştırabilir olmasını zorunlu kılar.
***************************** int main()
{
vector<string> svec{
"Mercury",
"Venus",
"Earth",
"Mars",
"Jupiter",
"Saturn"
};
ranges::copy(svec, ostream_iterator<string>(cout, "\n"));
if (auto iter = ranges::find(svec, 15, [](const std::string& s){ return s.size(); }); iter != svec.end())
{
cout << "found : " << *iter << "\n";
}
}
*****************************
AÇIKLAMA :
ranges::find fonksiyonuna:
-> range: svec
-> aranan değer: 15 (int)
-> projection: [](const std::string& s){ return s.size(); }
parametreleri verilmiştir.
--------------------------------------------------------------------
find algoritması her eleman için önce şu işlemi yapar:
proj(*it) → s.size()
-> Yani iteratörün işaret ettiği std::string değil, string’in uzunluğu karşılaştırmaya sokulur.
--------------------------------------------------------------------
Dolayısıyla algoritmanın karşılaştırdığı ifade:
s.size() == 15
--------------------------------------------------------------------
ranges::find(svec, 15, projection) çağrısı:
-> uzunluğu 15 olan ilk string’i bulur,
-> iteratörü döndürür,
-> eşleşme yoksa svec.end() döndürür.
***************************** std::identity
c++20 ile standartlara eklenmiştir.
struct identity;return value
std::forward<T>(t).std::identity::operator()
template< class T >
constexpr T&& operator()( T&& t ) const noexcept;
C++20 ile gelen std::identity, verilen değeri hiç değiştirmeden geri döndüren bir projeksiyon fonksiyonu nesnesidir (function object).
std::identity{}(val) // val
// “Aldığını aynen geri veren fonksiyon.” (identity function)C++20 ranges algoritmalarında, comparator ve projection parametreleri vardır:
std::ranges::sort(range, comp, proj);
// Eğer projeksiyon verilmezse default olarak std::identity kullanılır.proj(elem) → elem
/*
Projeksiyon yoksa yani "elemanı olduğu gibi kullan" demek istediğimiz yerlerde std::identity devreye girer.
*/std::identity nasıl tanımlıdır?
struct identity {
template <typename T>
constexpr T&& operator()(T&& t) const noexcept {
return std::forward<T>(t);
}
};
/********************************
Sadece aldığı nesneyi perfect forwarding ile geri verir
noexcept ve constexpr
********************************/sort() default projection
Yani elemanları doğrudan < ile karşılaştırır.
std::ranges::sort(v);
// comparator: std::ranges::less
// projection: std::identityexplicit olarak kullanma
Hiçbir değişiklik yapılmadan direkt v’nin elemanları karşılaştırılır.
std::ranges::sort(v, std::ranges::less{}, std::identity{});find_if ile
Burada da elemanların kendisi üzerinden equality yapılır.
auto it = std::ranges::unique(v, std::ranges::equal_to{}, std::identity{});Algoritmalar için varsayılan projection = std::identity
Zero-overhead (compiler inline edip yok eder)
Generic kodda projection parametresi gerektiğinde “hiçbir şey yapma” davranışı sağlar
identity fonksiyonunu kodda elle yazmaya gerek kalmaz
std::identity = no-op projection
Elemanı transformation’dan geçir (proj(e))
Comparator’a ver (comp(proj(a), proj(b)))
std::accumulate
c++20 ile standartlara eklenmiştir.
numeric başlık dosyasındadır.
template <class InputIt, class T>
constexpr T accumulate(InputIt first, InputIt last, T init)
{
for (; first != last; ++first)
{
init = std::move(init) + *first;
}
return init;
}template <class InputIt, class T, class BinaryOperation>
constexpr T accumulate(InputIt first, InputIt last, T init, BinaryOperation op)
{
for (; first != last; ++first)
{
init = op(std::move(init), *first);
}
return init;
}template <std::ranges::input_range R>
void print(const R& r)
{
for (const auto& x : r)
{
std::cout << x << ' ';
}
std::cout << '\n';
}
int main()
{
vector<string> svec{
"Mercury",
"Venus",
"Earth",
"Mars",
"Jupiter",
"Saturn"
};
print(svec);
auto acc = std::accumulate(svec.begin(), svec.end(), string{ "sum : " });
cout << acc << "\n";
}
*****************************
AÇIKLAMA :
*****************************
CEVAP :
Mercury Venus Earth Mars Jupiter Saturn
sum : MercuryVenusEarthMarsJupiterSaturn
*****************************int main()
{
vector<int> ivec{ 4, 6, 8, 1, 8, 9, 7, 2, 3 };
auto acc = std::accumulate(ivec.begin(), ivec.end(), 1, [](int x, int y){ return x * y; });
cout << acc << "\n";
}
*****************************
AÇIKLAMA :
*****************************
CEVAP : hepsinin çarpımının sonucunu yazdırır.
*****************************int main()
{
vector<string> svec{
"Mercury",
"Venus",
"Earth",
"Mars",
"Jupiter",
"Saturn"
};
print(svec);
const auto fn = [](size_t len, const string& s) {
return s.length() + len;
};
auto acc = std::accumulate(svec.begin(), svec.end(), 0u, fn);
cout << acc << "\n";
}
*****************************
AÇIKLAMA : uzunlukları hesapladı.
*****************************
CEVAP :
Mercury Venus Earth Mars Jupiter Saturn
34
*****************************int main ()
{
vector<int> ivec{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };
auto rng = ivec | std::views::filter([](int x){
return x % 10 == 7;
});
auto sum = std::accumulate(rng.begin(), rng.end(), 0);
cout << sum << "\n"; // 61
}
*****************************
AÇIKLAMA :
ivec | views::filter(...)
-> ivec üzerindeki elemanları süzen bir filter_view üretir.
Predicate: x % 10 == 7
-> son basamağı 7 olan sayılar seçilir:
rng.begin() ve rng.end()
-> aynı türden iterator verir.
Çünkü filter_view CommonRange veya “common_view üzerinden commonlaştırılmış” bir view türüdür.
std::accumulate (STL algoritması) yalnızca şu imzayı ister:
-> accumulate(InputIt first, InputIt last, T init)
first ve last aynı iterator türünde olmak zorundadır.
Eğer rng bir common range olmasaydı (örneğin sentinel’li bir range olsaydı):
-> rng.begin() != rng.end() // türler farklı olurdu
ve std::accumulate(rng.begin(), rng.end(), 0) derleme hatası olurdu.
*****************************
CEVAP : 7, 17, 37 → toplamları: 61
*****************************ranges::views
range yerine view oluşturulduğunda :
constant time da kopyalama ya da taşınma garantisi elde edilir.
range based for loop ta kullanılabilir.
std::ranges::views
std::views
*****************************
AÇIKLAMA : namespace alias kullanıldığında 2 kullanımda aynıdır.
alltaki yazım tercih edilir.
***************************** #include <iostream>
#include <numeric>
#include <vector>
#include <ranges>
using namespace std;
template <std::ranges::input_range R>
void print(const R& r)
{
for (const auto& x : r)
{
std::cout << x << ' ';
}
std::cout << '\n';
}
int main()
{
vector<int> ivec{ 13, 15, 85, 45, 12, 65, 47, 58, 99, 14, 52, 56 };
print(ivec);
for (auto val : std::views::take(ivec, 10))
{
cout << val << " ";
}
/*ranges::for_each(views::take(ivec, 10), [](int val) {
cout << val << " ";
});*/
}
*****************************
CEVAP :
13 15 85 45 12 65 47 58 99 14 52 56
13 15 85 45 12 65 47 58 99 14
*****************************int main()
{
vector<int> ivec{ 13, 15, 85, 45, 12, 65, 47, 58, 99, 14, 52, 56 };
print(ivec);
for (auto val : views::filter(ivec, [](int x){ return x % 5 == 0; }))
{
cout << val << " ";
}
}
*****************************
CEVAP :
13 15 85 45 12 65 47 58 99 14 52 56
15 85 45 65
*****************************int main()
{
vector<int> ivec{ 13, 15, 85, 45, 12, 65, 47, 58, 99, 14, 52, 56 };
print(ivec);
auto vw = views::take(ivec, 10); //ivec | views::take(10);
}
*****************************
AÇIKLAMA : ikisi de aynı yazımdır ve "take" adaptordur.
***************************** int main()
{
vector<int> ivec{ 13, 15, 85, 45, 12, 65, 47, 58, 99, 14, 52, 56 };
print(ivec);
auto vw = views::reverse(ivec);
print(vw);
}
*****************************
AÇIKLAMA : reverse sadece range alır range geri döner.
döndürdüğü değer bir view.
*****************************
CEVAP :
13 15 85 45 12 65 47 58 99 14 52 56
56 52 14 99 58 47 65 12 45 85 15 13
*****************************int main()
{
vector<int> ivec{ 13, 15, 85, 45, 12, 65, 47, 58, 99, 14, 52, 56 };
print(ivec);
auto vw = views::reverse(views::take(ivec, 5));
print(vw);
}
*****************************
CEVAP :
13 15 85 45 12 65 47 58 99 14 52 56
12 45 85 15 13
*****************************int main()
{
vector<int> ivec{ 13, 15, 85, 45, 12, 65, 47, 58, 99, 14, 52, 56 };
print(ivec);
for (auto val : views::filter(views::reverse(views::take(ivec, 10)), [](int x){ return x % 2 == 0; }))
{
cout << val << " ";
}
}
*****************************
AÇIKLAMA :
ivec container içindeki ilk 10 öğenin tersindeki 2 ye bölünenleri temsil eder.
views::filter(views::reverse(views::take(ivec, 10)), [](int x){ return x % 2 == 0; })
ifadesi bir range döner.
*****************************
CEVAP :
13 15 85 45 12 65 47 58 99 14 52 56
14 58 12
*****************************int main()
{
vector<int> ivec{ 13, 15, 85, 45, 12, 65, 47, 58, 99, 14, 52, 56 };
print(ivec);
auto vw = ivec | views::take(10) | views::reverse | views::filter([](int x){ return x % 2 == 0; });
for (const auto& x : vw)
{
std::cout << x << ' ';
}
}#include <format>
int main()
{
vector<int> vec{ 2, 5, 7, 9, 1 };
cout << format("{}\n", vec);
}
*****************************
AÇIKLAMA :
std::format yalnızca formatlanabilir türleri destekler; std::vector<T> için bir formatlayıcı (std::formatter<std::vector<T>>) standart kütüphanede yoktur.
*****************************
CEVAP : derleme zamanı hatası (syntax error / no matching formatter).y
*****************************Concepts (std::ranges)
Concept | Requires |
view | Range that is cheap to copy or move and assign |
viewable_range | Range that can be converted to a view (with ranges::all()) |
borrowed_range | Iterators not tied to the lifetime of the range |
common_range | begin and end (sentinel) have the same type |
Generic Range Element Functions
Function | Meaning |
std::ranges::empty(rg) | Yields whether the range is empty |
std::ranges::size(rg) | Yields the size of the range |
std::ranges::ssize(rg) | Size as a signed type |
std::ranges::begin(rg) | Iterator to the first element |
std::ranges::end(rg) | Sentinel (iterator to end) |
std::ranges::cbegin(rg) | Constant iterator to first element |
std::ranges::cend(rg) | Constant sentinel |
std::ranges::rbegin(rg) | Reverse iterator |
std::ranges::rend(rg) | Reverse sentinel |
std::ranges::crbegin(rg) | Reverse constant iterator |
std::ranges::crend(rg) | Reverse constant sentinel |
std::ranges::data(rg) | Raw data pointer |
std::ranges::cdata(rg) | Raw data pointer (const) |
Range Navigation Functions
Function | Meaning |
std::ranges::distance(from, to) | Number of elements between from and to |
std::ranges::distance(rg) | Number of elements in rg (even without size()) |
std::ranges::next(pos) | Next element after pos |
std::ranges::next(pos, n) | n-th next element after pos |
std::ranges::next(pos, to) | Position to behind pos |
std::ranges::next(pos, n, maxpos) | n-th element but not behind maxpos |
std::ranges::prev(pos) | Element before pos |
std::ranges::prev(pos, n) | n-th element before pos |
std::ranges::prev(pos, n, minpos) | n-th element before pos but not before minpos |
std::ranges::advance(pos, n) | Move pos forward/backward n elements |
std::ranges::advance(pos, to) | Move pos to 'to' |
std::ranges::advance(pos, n, maxpos) | Move pos, clamp to maxpos |
Swapping & Moving
Function | Meaning |
std::ranges::swap(val1, val2) | Swap two values (move semantics) |
std::ranges::iter_swap(pos1, pos2) | Swap values at two iterators |
std::ranges::iter_move(pos) | Return the value referenced by iterator pos (for move) |
Comparison Functions
Function | Meaning |
std::ranges::equal_to(val1, val2) | val1 == val2 |
std::ranges::not_equal_to(val1, val2) | val1 != val2 |
std::ranges::less(val1, val2) | val1 < val2 |
std::ranges::greater(val1, val2) | val1 > val2 |
std::ranges::less_equal(val1, val2) | val1 <= val2 |
std::ranges::greater_equal(val1, val2) | val1 >= val2 |
C++20 öncesi std::transform hatırlatma
template <std::ranges::input_range R>
void print(const R& r)
{
for (const auto& x : r)
cout << x << ' ';
cout << '\n';
}
int main()
{
vector<string> svec{
"Mercury",
"Venus",
"Earth",
"Mars",
"Jupiter",
"Saturn"
};
vector<size_t> dvec(svec.size());
// isimleri yazdır
ranges::copy(svec, ostream_iterator<string>{ cout, " " });
cout << '\n';
// C++20 öncesi std::transform: svec -> dvec (string uzunlukları)
auto iter = std::transform(svec.begin(), svec.end(), dvec.begin(),
[](const string& s){ return s.size(); });
// dvec içeriğini
print(dvec);
cout << "distance : " << std::distance(dvec.begin(), iter) << "\n";
}
*****************************
AÇIKLAMA : 1.overload
std::transform(first, last, dest, op) çağrısı:
-> [first, last) aralığındaki her eleman için op(*it) hesaplar,
-> sonucu *dest’e yazar ve dest’i ilerletir,
-> en son yazılan elemandan bir sonraki konumu (output iterator) döndürür.
---------------------------
auto iter = std::transform(svec.begin(), svec.end(), dvec.begin(), ...);
-> svec 6 elemanlı,
-> dvec.begin()’den başlayarak 6 eleman dolduruluyor,
-> dönen iter, dvec.begin() + 6 (yani dvec.end()) oluyor.
---------------------------
- Bu örnek, std::transform’un birinci overload’una (input range + output iterator) klasik bir örnektir.
*****************************
CEVAP :
Mercury Venus Earth Mars Jupiter Saturn
7 5 5 4 7 6
distance : 6
*****************************template<class InputIt1, class InputIt2, class OutputIt, class BinaryOperation>
OutputIt transform(InputIt1 first1, InputIt1 last1,
InputIt2 first2,
OutputIt d_first,
BinaryOperation op);
*****************************
AÇIKLAMA : 2.overload
Yani iki ayrı input range alır ve ikisinin eşleşmiş elemanlarına binary operation uygular.
*****************************
CEVAP :
std::transform(first1, last1, first2, dest, op)
-> binary-operation uygulayan 2. overload’dur.
----------------------------------------------------
v1 = {1,2,3,4,5}
v2 = {10,20,30,40,50}
result = v1[i] + v2[i]
----------------------------------------------------
-> std::transform’un binary overload’u, iki input range’in elemanlarını eşleştirip bir output range’e yazar.
*****************************#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
template <typename R>
void print(const R& r)
{
for (const auto& x : r)
cout << x << ' ';
cout << '\n';
}
int main()
{
vector<int> v1{ 1, 2, 3, 4, 5 };
vector<int> v2{ 10, 20, 30, 40, 50 };
vector<int> result(v1.size());
// Binary transform: v1[i] + v2[i]
auto iter = std::transform(
v1.begin(), v1.end(), // 1. input range
v2.begin(), // 2. input range
result.begin(), // output
[](int a, int b) { return a + b; }
);
print(result);
// Kaç eleman ?
cout << "distance : " << std::distance(result.begin(), iter) << "\n";
}
*****************************
AÇIKLAMA : 2.overload
*****************************
CEVAP :
11 22 33 44 55
distance : 5
*****************************std::ranges::transform
#include <algorithm>
template <class It>
void print(std::string_view msg, It beg, It end)
{
cout << msg;
for (auto pos = beg; pos != end; ++pos)
{
cout << ' ' << *pos;
}
cout << '\n';
}
int main()
{
vector<int> inColl{ 1, 2, 3, 4, 5, 6, 7 }; // src
vector<int> outColl{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // dst
auto result = std::ranges::transform(
inColl, // input range
outColl.begin(), // output başlangıcı
[](int val) { return val * val; } // unary operation
);
print("processed in ", inColl.begin(), result.in);
print("rest of in ", result.in, inColl.end());
print("written out ", outColl.begin(), result.out);
print("rest of out ", result.out, outColl.end());
}
*****************************
AÇIKLAMA :
std::ranges::transform(inColl, outColl.begin(), op) çağrısının dönüş tipi:
---------------------------------------------------------------------
std::ranges::transform_result<
std::ranges::borrowed_iterator_t<decltype(inColl)>, // result.in
std::vector<int>::iterator // result.out
>
---------------------------------------------------------------------
Bu struct iki bilgi taşır:
-> result.in → giriş range’inde işlenen son elemandan sonraki iterator
(bu örnekte inColl.end(); yani tüm range işlendi).
-> result.out → çıkış range’inde son yazılan elemandan sonraki iterator
(outColl.begin() + 7; yani ilk 7 elemana yazılmış).
*****************************
CEVAP :
processed in 1 2 3 4 5 6 7
rest of in
written out 1 4 9 16 25 36 49
rest of out 8 9 10
*****************************New return type for range algorithm
Type | Meaning | Members |
std::ranges::in_in_result | For the positions of two input ranges | in1, in2 |
std::ranges::in_out_result | For one position of an input range and one position of an output range | in, out |
std::ranges::in_in_out_result | For the positions of two input ranges and one position of an output range | in1, in2, out |
std::ranges::in_out_out_result | For one position of an input range and the position of two output ranges | in, out1, out2 |
std::ranges::in_fun_result | For one position of an input range and a function | in, out |
std::ranges::min_max_result | For one maximum and one minimum position/value | min, max |
std::ranges::in_found_result | For one position of an input range and a Boolean value | in, found |
ranges::minmax_element
int main ()
{
vector ivec{ 4, 6, 8, 9 };
auto [min, max] = ranges::minmax_element(ivec);
}
*****************************
AÇIKLAMA : Range içindeki en küçük ve en büyük elemanları bulur.
.min → en küçük elemana işaret eden iterator
.max → en büyük elemana işaret eden iterator
*****************************
CEVAP :
ivec = {4, 6, 8, 9}
min → ivec.begin() (4)
max → ivec.begin() + 3 (9)
*****************************int main ()
{
vector ivec{ 4, 6, 8, 9 };
auto minmax = ranges::minmax_element(ivec);
minmax.min
minmax.max
}
*****************************
AÇIKLAMA :
ivec içindeki en küçük ve en büyük elemana işaret eden iki iterator döndürür.
Geri dönüş tipi:
-> std::ranges::minmax_element_result<iterator>
iki üyeye sahiptir:
-> min → minimum elemana işaret eden iterator
-> max → maximum elemana işaret eden iterator
- minmax.min ve minmax.max iterator türündedir; değer değil, elemanlara giden adres gibidir.
*****************************
CEVAP :
minmax.min → ivec.begin() → 4
minmax.max → ivec.begin() + 3 → 9
*****************************ranges::find
int main ()
{
vector ivec{ 4, 6, 8, 9 };
auto iter = ranges::find(ivec, 8);
if (iter != ivec.end())
{
cout << *iter;
}
}
*****************************
AÇIKLAMA : Bulursa o konuma işaret eden iterator döndürür; bulamazsa ivec.end() döndürür.
Iterator, bir “pointer benzeri” nesnedir;
*****************************
CEVAP : 8
*****************************views concept içindekilerin range döndürmesi ve sınanması
int main ()
{
vector ivec{ 4, 6, 8, 9, 5, 6, 3 };
views::take(ivec, 4);
}
*****************************
AÇIKLAMA :
-> views::take(ivec, 4) bir range adaptor’dür.
-> ivec’in ilk 4 elemanını kapsayan bir view döndürür.
-> Dönen nesnenin gerçek türü:
std::ranges::take_view<std::ranges::ref_view<std::vector<int>>>
------------------------------------------------------
Bu bir lazy (ertelemeli) range’dir; yani:
-> veri içermez,
-> ivec üzerindeki ilk 4 elemana görüntü (view) sağlar.
*****************************
CEVAP : Çağrı sadece view döndürür, fakat sen onu kullanmadığın için uyarı olur
*****************************views concept sınama
int main ()
{
vector ivec{ 4, 6, 8, 9, 5, 6, 3 };
auto vw = views::take(ivec, 4);
static_assert(ranges::view<decltype(vw)>);
}
*****************************
AÇIKLAMA :
1. views::take(ivec, 4) ifadesi bir view adaptor çağrısıdır ve vw değişkeninin türü bir view türüdür (kabaca take_view<ref_view<vector<int>>>).
2. ranges::view<T> bir concept;
T türü bir view değilse, static_assert(ranges::view<decltype(vw)>); satırı derleme hatası üretirdi.
3. vw gerçekten bir view türündedir,
elemanların sahibi değildir, sadece ivec üzerindeki ilk 4 elemene hafif bir “pencere” açar.
*****************************
CEVAP : legal
*****************************views concept boyut
int main ()
{
vector ivec{ 4, 6, 8, 9, 5, 6, 3 };
auto vw = views::take(ivec, 4);
cout << sizeof(vw) << "\n";
}
*****************************
AÇIKLAMA :
views::take(ivec, 4) ifadesi bir view adaptor çağrısıdır ve vw'nun türü kabaca:
-> std::ranges::take_view<std::ranges::ref_view<std::vector<int>>>
---------------------------------------------------------------------
-> ref_view → ivec için bir pointer (veya eşdeğer yapıda bir iterator çifti)
-> take_view → sadece kaç eleman alınacağını tutan std::ranges::range_difference_t<R> yani bir sayısal değer
---------------------------------------------------------------------
Dolayısıyla bir take_view genellikle şunlardan oluşur:
-> 1 adet pointer / iterator → 8 byte
-> 1 adet count (int / long / ptrdiff_t) → 4–8 byte
-> padding ile birlikte → 8 veya 16 byte
*****************************
CEVAP : 8
*****************************fonksiyonun çağırılması için lazy evaluation gerekir.
Nasıl bir range döndürürler ?
Bunlar birer fonksiyon nesneleridir. kodlar template olduğundan ve fonksiyon çağrı operatorlerinin geri dönüş değeri compile-time da belirlenir.
int main ()
{
vector ivec{ 4, 6, 8, 9, 5, 6, 3 };
auto iter = std::views::filter(ivec, [](int x) {
cout << "filter called\n";
return x % 3 == 0;
});
}
*****************************
AÇIKLAMA :
std::views::filter bir range adaptor:
-> Çağrı views::filter(ivec, pred) bir filter_view döndürür.
---------------------------------------------------------------------
ivec’e dokunulmaz,
lambda ([](int x){ ... }) hiç çağrılmaz,
sadece “ivec + predicate” bilgisini tutan lazy view nesnesi (fv) oluşturulur.
---------------------------------------------------------------------
Yani henüz elemanlara erişilmediği için predicate çalışmaz.
***************************** int main ()
{
vector ivec{ 4, 6, 8, 9, 5, 6, 3 };
auto iter = std::views::filter(ivec, [](int x) {
cout << "filter called\n";
return x % 3 == 0;
});
auto val = iter.begin();
}
*****************************
AÇIKLAMA :
fv.begin() çağrıldığında, filter_view ilk elemanı bulmak için alttaki range üzerinde dolaşır:
-> ivec = {4, 6, 8, 9, 5, 6, 3}
-> Predicate: x % 3 == 0
---------------------------------------------------------------------
begin() içeride şunu yapar:
-> x = 4 → predicate çağrılır → "filter called" yazılır → 4 % 3 != 0 → devam
-> x = 6 → predicate çağrılır → "filter called" yazılır → 6 % 3 == 0 → DUR → iterator buraya konumlanır
*****************************
CEVAP :
filter called
filter called
*****************************Arka planda ne var bakalım ?
STL tarafında :
“Taban sınıf olarak kullanılan view interface isimli Base class vardır. Tipik olarak bir CRTP örüntüsüyle taban sınıfın interface'indeki fonksiyonları türemiş sınıflara eklenir.”
namespace std::ranges {
template <class Derived>
class view_interface;
}
*****************************
AÇIKLAMA :
Bu bir CRTP base class:
-> Derived = senin view sınıfın
-> view_interface<Derived>’den türeyerek, Derived’e ortak interface fonksiyonları kazanır.
***************************** “View olmak için” bu base class zorunlu değildir.
View olmak için gereken şey:
ranges::range<T> olması,
ranges::view<T> konseptini sağlamak (cheap-to-copy, non-owning, vs.)
“N elemanlık ilk kısmı gösteren basit bir view” (tam bir take_view değil ama fikir aynı):
template <std::ranges::view V>
class first_n_view : public std::ranges::view_interface<first_n_view<V>> {
V base_;
std::size_t count_;
public:
first_n_view() = default;
first_n_view(V base, std::size_t count)
: base_{std::move(base)}, count_{count} {}
V base() const { return base_; }
auto begin() {
return std::ranges::begin(base_);
}
auto end() {
auto it = std::ranges::begin(base_);
auto last = std::ranges::end(base_);
std::size_t n = count_;
while (n-- && it != last)
++it;
return it;
}
};
*****************************
AÇIKLAMA :
first_n_view v{std::views::all(ivec), 3};
v.empty(); // varsa begin==end'den hesaplanır
v.front(); // begin()'den hesaplanır
bunlar otomatik gelir.
*****************************ranges composable(birleştirilebilir) olması ne demek ?
int main ()
{
vector ivec{ 4, 6, 8, 9, 5, 6, 3, 12, 14, 25 };
vector<int> tar;
vector<int> dest;
copy_if(ivec.begin(), ivec.end(), back_inserter(tar),
[](int x){ return x % 2 == 0; });
transform(tar.begin(), tar.end(), back_inserter(dest),
[](int x){ return x * x; });
for (auto val : dest)
{
cout << val << " ";
}
}
*****************************
AÇIKLAMA : C++20 öncesi STL de kullanıma örnek
-> copy_if ile ivec içinden çift sayıları tar vektörüne kopyalıyoruz.
-> transform ile tar içindeki her değerin karesini dest içine yazıyoruz.
---------------------------------------------------------------------
İki ayrı ara container (tar, dest) kullanılıyor; algoritmalar compose edilmiyor, sırayla çağrılıyor.
*****************************
CEVAP : 16 36 64 36 144 196
*****************************int main ()
{
namespace rng = std::ranges;
namespace vw = std::views;
vector ivec{ 4, 6, 8, 9, 5, 6, 3, 12, 14, 25 };
//vw::filter(vec, [](int x){ return x % 2 == 0; }); // view range döndürdü.
auto ret = vw::transform(vw::filter(ivec,[](int x){ return x % 2 == 0; }),
[](int x){ return x * x; }));
for (auto val : ret)
{
cout << val << " ";
}
}
*****************************
AÇIKLAMA : C++20 ile
C++20 ile range adaptörleri kullanıyoruz:
-> vw::filter(ivec, pred) → ivec üzerinde çift sayıları süzen bir view döndürür.
-> vw::transform(view, op) → o view’in her elemanına op uygulayan yeni bir view döndürür.
---------------------------------------------------------------------
ret bir view:
-> alttaki range: ivec,
-> filter: x % 2 == 0,
-> transform: x * x.
---------------------------------------------------------------------
Hiç ara container yok; filtreleme + dönüştürme lazy ve composable şekilde birleştirilmiştir.
*****************************
CEVAP : 16 36 64 36 144 196
*****************************int main ()
{
namespace vw = std::views;
vector<int> ivec{ 4, 6, 8, 9, 5, 6, 3, 12, 14, 25 };
auto rng = ivec
| vw::filter([](int x){ return x % 2 == 0; })
| vw::transform([](int x){ return x * x; });
for (auto val : rng)
{
cout << val << " ";
}
}
*****************************
AÇIKLAMA : C++20 ile pipe (|) kullanarak composable views
ivec | vw::filter(...) | vw::transform(...)
Data akışı sol → sağ okunuyor:
1. ivec’ten başlayıp
2. filtreden geçiyor (çift sayılar),
3. transform’dan geçiyor (karesi alınıyor).
---------------------------------------------------------------------
rng yine bir view:
-> Hiç kopya yok,
_> Elemanlara for-range ile erişildikçe, filter + transform lazy uygulanıyor.
*****************************
CEVAP : 16 36 64 36 144 196
*****************************views::iota
int main ()
{
namespace rng = std::ranges;
namespace vw = std::views;
for (auto val : vw::iota(10) | vw::take(5))
{
cout << val << " ";
}
}
*****************************
AÇIKLAMA :
-> vw::iota(10) → 10’dan başlayarak sonsuz artan bir sayı dizisi üreten view (10, 11, 12, 13, …).
-> Bu view lazy, yani “sonsuz” olmasına rağmen, sadece tükettiğin kadar eleman üretir.
-> vw::take(5) → gelen range’in ilk 5 elemanını gösteren view.
-> Pipe (|) ile birleştirince:
vw::iota(10) | vw::take(5) şu sıralamayı üretir: 10, 11, 12, 13, 14.
*****************************
CEVAP : 10 11 12 13 14
*****************************int main ()
{
namespace rng = std::ranges;
namespace vw = std::views;
for (auto val : vw::iota(10, 15))
{
cout << val << " ";
}
}
*****************************
AÇIKLAMA :
vw::iota(10, 15) → [10, 15) aralığını üreten sonlu bir iota view:
-> 10 dahil,
-> 15 hariç.
Yani üretilen değerler: 10, 11, 12, 13, 14.
Bu da lazy: elemanlar sadece iterasyon sırasında üretiliyor, önceden bir konteynıra doldurulmuyor.
*****************************
CEVAP :
10 11 12 13 14
*****************************int main ()
{
namespace rng = std::ranges;
namespace vw = std::views;
int low, high;
cout << "iki sayi gir : ";
cin >> low >> high;
for (auto val : vw::iota(low, high) | vw::reverse | vw::filter(isprime))
{
cout << val << " ";
}
}
*****************************
AÇIKLAMA :
vw::iota(low, high):
-> [low, high) aralığındaki tamsayıları üreten sonlu bir view.
vw::reverse:
-> Bu aralığı tersten gösteren bir view:
-> [low, high) yerine, sıralama: high-1, high-2, ..., low.
vw::filter(isprime):
-> isprime(int) isimli unary predicate kullanılarak,
-> sadece isprime(x) == true olan elemanları geçiren bir filter view.
---------------------------------------------------------------------
Girilen [low, high) aralığındaki asal sayıları, büyükten küçüğe olacak şekilde ekrana yazdırır.
***************************** vw::iota(low, high) | vw::reverse | vw::filter(isprime) | rng::to<std::vector>()
bu range'deki öğelerden container nasıl oluşturulur.
#include <iostream>
#include <vector>
#include <ranges>
using namespace std;
bool isprime(int x); // asal kontrol eden bir fonksiyon olsun.
template <std::ranges::input_range R>
void print(const R& r)
{
for (const auto& v : r)
cout << v << ' ';
cout << '\n';
}
int main ()
{
namespace rng = std::ranges;
namespace vw = std::views;
int low, high;
cout << "iki sayi gir : ";
cin >> low >> high;
// C++23: ranges::to ile view'dan std::vector üret
auto vec = vw::iota(low, high)
| vw::reverse
| vw::filter(isprime)
| rng::to<std::vector<int>>();
cout << vec.size() << "\n";
print(vec);
}
*****************************
AÇIKLAMA : c++23
-> vw::iota(low, high) → [low, high) aralığını üreten lazy iota view.
-> vw::reverse → bu aralığı tersten gösteren view.
-> vw::filter(isprime) → isprime(int) unary predicate’i ile asal olanları filtreleyen view.
-------------------------------------------------------------
vw::iota(low, high)
| vw::reverse
| vw::filter(isprime)
-> [low, high) aralığındaki asal sayıları büyükten küçüğe veren bir composed view.
-------------------------------------------------------------
https://en.cppreference.com/w/cpp/ranges/to.html
C++23 ile gelen std::ranges::to: rng::to<std::vector<int>>()
bu lazy view’i gerçek bir container’a dönüştürür:
-> View’in elemanlarını tek tek iter ederek,
-> std::vector<int> içine toplar.
yani auto vec = ... | rng::to<std::vector<int>>(); ile elde edilen range’i std::vector<int>’e yazar.
***************************** std::views:::repeat
c++23
ne parametre geçilirse onu döndürür.
int main ()
{
namespace rng = std::ranges;
namespace vw = std::views;
for (int x : vw::repeat(5) | vw::take(10))
cout << x << ' ';
}
*****************************
AÇIKLAMA : repeat_view, değeri sonsuz kez tekrar eden bir infinite view üretir:
-> vw::take(10) → bu infinite view’in sadece ilk 10 elemanını alan bir take_view oluşturur:
-> repeat | take bir composed range üretmiştir
*****************************
CEVAP : 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
*****************************std::views:::common
c++20 ile gelen bir adaptor
common range döndürür.
begin ve end türleri aynı olan demektir.
eğer common' a gönderilen range comman niteliğinde bir range ise aynısını geri döndürür, değilse eğer ref view döndürür.
begin ve end return değerleri farklıysa common view nesnesi döndürür.
int main ()
{
vector<int> src(100);
vector<int> dst;
copy(src.begin(), src.end(), dest.begin()); // tanımsız davranıştır.
copy(src.begin(), src.end(), back_inserter(dst)); // legal
}
*****************************
AÇIKLAMA :
dst başlangıçta boş bir vektör:
-> dst.begin() → dst.end() ile aynı konum (geçerli ama hiçbir elemana ait olmayan iterator).
-> copy(src.begin(), src.end(), dst.begin());
boş vektörde eleman olmayan bir konuma yazmaya çalıştığı için tanımsız davranış (UB).
***************************** back_inserter(dst) ise bir function template:
template <typename Container>
std::back_insert_iterator<Container> back_inserter(Container& c)
{
return std::back_insert_iterator<Container>(c);
}
*****************************
AÇIKLAMA :
-> Geri dönüş türü: std::back_insert_iterator<Container>.
-> İçeride, referans semantiğiyle sarmalanmış bir Container* tutar.
-> Bu iterator’ın: operator++(), operator++(int) gibi artırma operatörleri no-op (hiçbir şey yapmaz, sadece *this döndürür).
-> operator= ise
it = value; // it: back_insert_iterator
container.push_back(value); anlamına gelir.
Yani atama operatörü, aldığı değeri sarmaladığı container’ın push_back() fonksiyonuna dönüştürür.
*****************************src’deki tüm elemanları sırayla dst.push_back(...) ile ekler; dst başlangıçta boş olsa bile tamamen legal ve güvenlidir.
int main ()
{
std::list<int> mylist(10);
auto mytake = std::views::take(mylist, 4);
auto myiota = std::views::iota(35, 45);
auto x = std::views::common(mylist); // x is a ref_view
auto y = std::views::common(mytake); // y is a common_view
auto z = std::views::common(myiota); // z is an iota_view
}
*****************************
AÇIKLAMA :
std::views::common(r) bir adaptor: Amacı; begin ve end türleri aynı olan bir common range elde etmek.
1. Eğer R : view değil ve common_range ise
-> std::views::common(R) → std::ranges::ref_view<R> döndürür.
2. Eğer R : view ama common_range değil ise
-> std::views::common(R) → std::ranges::common_view<R> döndürür.
3. Eğer R zaten : view ve common_range ise
-> std::views::common(R) → aynı view türünü döndürür (no-op).
***************************** Açıklamalı anlatım :
auto x = std::views::common(mylist);
std::list<int> : view değil, begin/end türü aynı → common_range.
x → std::ranges::ref_view<std::list<int>>.
---------------------------------------------------------------------
auto y = std::views::common(mytake);
mytake = std::views::take(mylist, 4);
-> take_view<ref_view<list<int>>> türünden bir view. Çoğu implementasyonda take_view’in begin ve end türleri farklı (iterator + sentinel).
-> yani y → std::ranges::common_view<decltype(mytake)>.
---------------------------------------------------------------------
auto myiota = std::views::iota(35, 45);
auto z = std::views::common(myiota);
std::views::iota(35, 45):
-> iota_view<int> bir view türüdür. Begin ve end aynı türden iterator kullanır (common).
-> z → yine iota_view<int>
(yani std::views::common burada no-op gibi davranır).Sentinel + subrange + views::common örneği :
template <auto ENDVAL>
struct Sentinel {
bool operator==(auto pos) const
{
return *pos == ENDVAL;
}
};
int main ()
{
vector<int> ivec(20);
ivec[5] = -1; // 5 tane sıfır olacak.
// range'in begin ve end türü aynı değildir. yani common_range değildir.
ranges::subrange sr(ivec.begin(), Sentinel<-1>());
auto cv = views::common(sr);
auto n = count(cv.begin(), cv.end(), 0);
cout << "n : " << n << "\n";
}
*****************************
AÇIKLAMA :
-> std::vector<int> ivec(20); tüm elemanlar başlangıçta 0.
-> ivec[5] = -1; şimdi dizi: 0, 0, 0, 0, 0, -1, 0, 0, ..., 0
---------------------------------------------------------------------
std::ranges::subrange sr(ivec.begin(), Sentinel<-1>());
sr:
-> begin() → ivec.begin() (iterator)
-> end() → Sentinel<-1>{} (sentinel)
Sentinel:
bool operator==(auto pos) const {
return *pos == ENDVAL; // ENDVAL = -1
}
Yani iterator index 5’teki -1’i gördüğünde “end” kabul edilir:
-> İterasyon: ivec[0]..ivec[4] → 5 eleman
-> ivec[5] == -1 olduğunda sentinel eşitliği sağlanır, döngü biter.
---------------------------------------------------------------------
auto cv = std::views::common(sr);
-> sr bir subrange<iterator, Sentinel> → begin/end türü farklı → common_range değil.
-> std::views::common(sr) → common_view<subrange<...>> üretir. cv.begin() ve cv.end() artık aynı türden iterator’dur.
---------------------------------------------------------------------
auto n = std::count(cv.begin(), cv.end(), 0);
-> cv üzerinde 0..4 indeksleri dolaşılır.
-> Bu aralıktaki elemanlar: 0, 0, 0, 0, 0 → 5 tane sıfır.
***************************** take_while
int main ()
{
vector<int> ivec{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };
auto rng = ivec | std::views::take_while([](int x){ return x < 20; });
// auto sum = std::accumulate(rng.begin(), rng.end(), 0); // error
static_assert(std::ranges::common_range<decltype(rng)>); // fail
}
*****************************
AÇIKLAMA :
std::views::take
→ baştan itibaren sabit sayıda (n) eleman alan bir view.
std::views::take_while(pred)
→ baştan itibaren predicate true döndürdüğü sürece elemanları alan bir view: İlk false gördüğü yerde range’i sonlandırır.
---------------------------------------------------------------------
auto rng = ivec | std::views::take_while([](int x){ return x < 20; });
-> ivec = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}
-> x < 20 olduğu sürece devam eder:
alacağı elemanlar: 2, 3, 5, 7, 11, 13, 17, 19
23 geldiğinde predicate false → burada durur.
-> std::views::take_while’ın döndürdüğü view çoğu implementasyonda:
begin() → bir iterator türü,
end() → farklı bir sentinel türü döndürür.
-> Yani bu view common_range değildir:
std::ranges::common_range<decltype(rng)> == false
---------------------------------------------------------------------
1. std::accumulate(rng.begin(), rng.end(), 0);
-> std::accumulate klasik STL algoritmasıdır (ranges versiyonu değil).
template<class InputIt, class T>
T accumulate(InputIt first, InputIt last, T init);
-> Burada first ve last aynı iterator türü olmak zorunda.
-> rng.begin() ile rng.end() farklı türler olduğu için bu çağrı derleme hatası verir.
2. static_assert(std::ranges::common_range<decltype(rng)>);
-> common_range konsepti, “begin ve end türleri aynıdır” şartını arar.
-> take_while view’i bu şartı sağlamadığı için:
std::ranges::common_range<decltype(rng)> → false
static_assert(false); → derleme zamanı hatası.
*****************************
CEVAP : Yani her iki durumda da kod derlenmez.
*****************************Her range, begin()/end() çiftinin aynı türde olduğu bir common_range değildir; özellikle sentinel kullanan view’lerde klasik STL algoritmaları (std::accumulate, std::copy vs.) direkt kullanılamaz.
Böyle durumda iki tip çözüm var :
Ya std::views::common ile common range’e dönüştürürsün :
auto crng = std::views::common(rng);
auto sum = std::accumulate(crng.begin(), crng.end(), 0); // artık legalYa da baştan std::ranges::fold / std::ranges::reduce gibi ranges dünyasına ait algoritmaları kullanırsın (C++23 tarafında).
farklı kullanım şekilleri ile örneklendirme :
int main ()
{
vector ivec{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };
auto rng = std::views::take_while([](int x){ return x < 20; });
auto com_rng = std::views::common(rng);
auto sum = std::accumulate(com_rng.begin(), com_rng.end(), 0);
cout << sum << "\n";
}
*****************************
AÇIKLAMA :
-> views::take_while([](int x){ return x < 20; })
ivec’in baştan itibaren 20’den küçük olan elemanlarını alır.
İlk >= 20 gördüğü yerde durur.
Bu dizide: 2, 3, 5, 7, 11, 13, 17, 19 alınır; 23’te durur.
-> auto rng = ivec | views::take_while(...);
Bu bir view (lazy range). begin ve end türleri farklı (iterator + sentinel) olabilir → common_range olmayabilir.
-> auto com_rng = std::views::common(rng);
common adaptörü, rng’i common_range hâline getirir.
Artık com_rng.begin() ve com_rng.end() aynı türden iterator döndürür.
-> std::accumulate(com_rng.begin(), com_rng.end(), 0);
Klasik STL algoritması olduğu için begin ve end aynı türde olmalı → common sayesinde bu sağlanır.
*****************************
CEVAP : 77
*****************************int main ()
{
vector ivec{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };
auto rng = ivec |
std::views::take_while([](int x){ return x < 10; }) | std::views::common;
auto sum = std::accumulate(rng.begin(), rng.end(), 0);
cout << sum << "\n";
static_assert(std::ranges::common_range<decltype(rng)>); // holds
}
*****************************
AÇIKLAMA : “type-lining ile farklı bir yöntem.
İlk örnekte: önce rng oluşturup sonra com_rng = std::views::common(rng) yaptık.
*****************************
CEVAP : 77
*****************************int main ()
{
vector ivec{ 2, 3, 5, 7 11, 13, 17, 19, 23, 29, 31, 37 };
auto rng = std::views::common(std::views::take_while(ivec,
(int x){ return x < 10; }));
auto sum = std::accumulate(rng.begin(), rng.end(), 0);
cout << sum << "\n";
static_assert(std::ranges::common_range<decltype(rng)>); // holds
}
*****************************
AÇIKLAMA : common ile yazdık
*****************************
CEVAP : 77
*****************************std::ranges::common_range
using myrng = std::vector<int>;
int main ()
{
static_assert(std::ranges::common_range<myrng>);
}
*****************************
AÇIKLAMA :
-> std::ranges::common_range<R> konsepti, iki şeyi ister:
1. R bir std::ranges::range olmalı,
2. std::ranges::begin(r) ve std::ranges::end(r) aynı türden olmalı (yani sentinel ve iterator tipi aynı).
-> std::vector<int> için:
begin() → iterator
end() → iterator
Yani myrng açıkça bir common_range’dir.
*****************************
CEVAP : derleme zamanında başarılı
*****************************int main ()
{
std::vector ivec{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };
static_assert(std::ranges::common_range<decltype(ivec | std::views::take(5))>);
}
*****************************
AÇIKLAMA :
-> ivec | std::views::take(5) ifadesi, ivec üzerinde ilk 5 elemanı gösteren bir take_view üretir.
-> std::views::take’in davranışı tabanda şuna bağlı:
** Eğer temel range (ivec) bir random_access_range ve sized_range ise
(ve std::vector için bu doğru),
** take_view’in begin() ve end() fonksiyonları aynı iterator türünü döndürür → common_range olur.
*****************************
CEVAP : take adaptörünün, std::vector gibi random-access + sized bir range üstünde hâlâ common_range özelliğini koruduğunu gösteriyoruz.
*****************************std::ranges::views::drop_while, std::ranges::drop_while_view
int main ()
{
vector<int> ivec{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };
auto fn = [](int x){ return x < 10; };
static_assert(std::ranges::common_range<
decltype(ivec | std::views::drop_while(fn))
>);
}
*****************************
AÇIKLAMA :
-> std::views::drop_while bir view adaptor:
** Baştan itibaren fn(x) true olduğu sürece elemanları düşer,
** ilk false’tan itibaren kalan kısmı gösterir.
-> drop_while’ın döndürdüğü view’in begin() ve end()’i:
** Çoğu implementasyonda farklı türdedir (iterator + sentinel).
** Yani bu view common_range değildir.
----------------------------------------------------------------------------
std::ranges::common_range<decltype(ivec | std::views::drop_while(fn))> false olur.
*****************************
CEVAP : derleme zamanı assertion hatası (static assertion failed).
*****************************std::ranges::dangling
std::ranges::dangling, C++20 Ranges tasarımında “geçici (rvalue) bir range üzerinden iterator döndüren algoritmaların, dangling iterator üretmesini engellemek için” kullanılan boş (empty) bir türdür.
Amaç: ranges::find, ranges::begin gibi iterator döndüren fonksiyonlar rvalue (geçici) container üzerinde çağrıldığında, gerçekte dönecek iterator’ın işaret ettiği bellek hemen geçersiz olacağı için, bunun “iterator gibi” kullanılmasını compile-time’da engellemeye yaklaşmaktır.
Ranges algoritmaları bu durumda iterator yerine std::ranges::dangling döndürür. Böylece hata genellikle iterator’ı kullanmaya çalıştığın yerde ortaya çıkar.
struct Data{};
Data foo();
int main ()
{
auto iter = foo();
/* Data türü iterator değildir.
operator* overload edilmediği için dereference edilemez. */
*iter;
}
*****************************
AÇIKLAMA :
Burada std::ranges::dangling ile doğrudan bir ilişki yok.
iter değişkeninin türü Data’dır ve iterator semantiği yoktur.
operator* tanımlı olmadığı için '*iter' ifadesi derleme hatası verir.
Bu örnek sadece “boş/empty tiplerin dereference edilememesi” fikrini gösterir.
*****************************
CEVAP :
Derleme hatası (operator* yok / Data dereference edilemez).
*****************************std::vector<int> get_vec()
{
return { 1, 3, 5, 7 };
}
int main()
{
namespace rng = std::ranges;
/* get_vec() rvalue (geçici) bir std::vector döndürür.
rng::find iterator döndüren bir algoritmadır.
Ranges tasarımı gereği:
- Eğer algoritmaya verilen range bir “borrowed_range” değilse
(std::vector borrowed değildir), dönüş tipi iterator olmak
yerine std::ranges::dangling olur.
Bu yüzden çağrı legal, ama dönüş tipi iterator değildir.
*/
auto iter = rng::find(get_vec(), 7); // legal
/* iter aslında std::ranges::dangling türündedir.
dangling bir empty struct’tır ve dereference edilemez.
*/
auto val = *iter;
}
*****************************
AÇIKLAMA :
- get_vec() çağrısı geçici bir std::vector üretir ve bu geçici ifade sonunda yok olur.
- Normalde iterator dönseydi, iterator yok olmuş vector’ün belleğini işaret edecekti (dangling iterator).
- Bunu engellemek için ranges algoritmaları “borrowed_range değilse” iterator yerine std::ranges::dangling döndürür.
- Böylece hata “iter’i iterator gibi kullanmaya çalıştığında” (ör. *iter) derleme zamanında yakalanır.
Özet: Bu mekanizma “rvalue container + iterator döndüren algoritma” kombinasyonunda güvenlik sağlar.
*****************************
CEVAP :
Derleme hatası: '*iter' geçersiz (iter std::ranges::dangling).
*****************************int main()
{
auto pos = std::ranges::find(std::vector{ 1, 2, 3 }, 3);
std::cout << *pos << "\n";
}
*****************************
AÇIKLAMA :
- std::vector{1,2,3} geçici (rvalue) bir container’dır ve ifade sonunda yok olur.
- std::vector bir borrowed_range değildir.
- Bu nedenle std::ranges::find iterator döndürmek yerine std::ranges::dangling döndürür.
- pos iterator değildir; dereference edilmeye çalışıldığında derleme hatası oluşur.
Not: “syntax error” değil, tip uyuşmazlığı/overload yokluğu kaynaklı derleme hatasıdır.
*****************************
CEVAP :
Derleme hatası: '*pos' geçersiz (pos std::ranges::dangling).
*****************************Özetle; geçici borrowed olmayan bir range (örn. geçici std::vector) üzerinde iterator döndüren ranges algoritmaları çağrılırsa dönüş tipi iterator değil std::ranges::dangling olur; böylece yanlış kullanım çoğu zaman derleme aşamasında yakalanır.
std::ranges::borrowed_range
Bu kavramın amacı, iterator döndüren ranges algoritmalarının, geçici (rvalue) bir range üzerinde çağrıldığında dangling iterator üretip üretmeyeceğini compile-time seviyesinde belirlemektir.
Bir range, iterator’ları, o range nesnesinin ömrü sona erdikten sonra bile güvenle kullanılabiliyorsa borrowed_range olarak kabul edilir.
Başka bir deyişle:
Range nesnesi yok olsa bile Iterator’ın işaret ettiği asıl veri yaşamaya devam ediyorsa bu range borrowed_range’dir.
Bir range’in borrowed_range olmasının iki temel yolu vardır
Range bir L-value ise
Eğer algoritmaya verilen range isimli bir nesne (lvalue) ise:
std::vector<int> v;
ranges::find(v, 5);
*****************************
AÇIKLAMA : Her lvalue range borrowed_range kabul edilir
-> v hâlâ hayatta olduğu için
-> Dönen iterator v’nin içindeki geçerli belleği işaret eder
-> Dangling riski yoktur
*****************************Range rvalue olsa bile iterator veriyle birlikte yaşayabiliyorsa
Bazı türler:
Verinin sahibi değildir
Sadece başka bir nesnenin verisini “görünüm” olarak tutar
Bu türler:
std::string_view
std::span
Birçok std::ranges::view
Bu türlerde:
View nesnesi geçici olabilir
Ama iterator’ın işaret ettiği veri başka bir nesneye aittir ve yaşamaya devam eder
Bu türler rvalue olsalar bile borrowed_range sayılır.
int main()
{
std::vector<int> ivec{ 2, 5, 8, 9 };
std::ranges::find(ivec, 6);
}
*****************************
AÇIKLAMA :
- ivec isimli bir nesnedir (lvalue).
- ranges::find iterator döndürür.
- ivec hâlâ hayatta olduğu için iterator dangling olmaz.
- Bu nedenle çağrı tamamen legaldir.
*****************************
CEVAP : legal
*****************************int main()
{
std::string name{ "yusuf" };
auto sv = std::string_view{ name };
auto pos = std::ranges::find(sv, 'y');
if (pos != sv.end())
std::cout << *pos << "\n";
else
std::cout << "not found\n";
}
*****************************
AÇIKLAMA :
- std::string_view bir view’dır; verinin sahibi değildir.
- string_view{name} name’in iç buffer’ına pointer tutar.
- sv geçici olsa bile, name hâlâ hayatta olduğu için
pos iterator’ı geçerli belleği işaret eder.
- std::string_view, ranges tarafından borrowed_range kabul edilir.
- Bu nedenle rvalue string_view ile iterator döndürmek güvenlidir.
Not:
string_view borrowed_range olsa bile,
işaret ettiği string’in ömrü biterse dangling oluşur.
*****************************
CEVAP : y
*****************************int main()
{
auto pos = std::ranges::find(std::vector{ 1, 2, 3 }, 3);
std::cout << *pos << "\n";
}
*****************************
AÇIKLAMA :
- std::vector{1,2,3} geçici bir container’dır (rvalue).
- std::vector borrowed_range değildir.
- ranges::find iterator döndürmek yerine
std::ranges::dangling döndürür.
- pos iterator değildir; dereference edilmeye çalışıldığında
derleme hatası oluşur.
*****************************
CEVAP : derleme hatası (pos = std::ranges::dangling)
*****************************int main()
{
int arr[]{ 1, 2, 3, 4 };
auto pos = std::ranges::find(std::span{ arr }, 3);
std::cout << *pos << "\n";
}
*****************************
AÇIKLAMA :
- std::span bir view’dır ve verinin sahibi değildir.
- span{arr} geçici olsa bile arr dizisi hâlâ hayattadır.
- iterator arr içindeki geçerli belleği işaret eder.
- std::span borrowed_range kabul edilir.
*****************************
CEVAP : 3
*****************************borrowed_range, “iterator’ı range nesnesinden ödünç alıp güvenle kullanabilme” garantisidir.
Lvalue range → her zaman borrowed
Rvalue range:
Eğer iterator’lar veriyle birlikte yaşayabiliyorsa → borrowed
Aksi halde → borrowed değil, dönüş tipi std::ranges::dangling
std::ranges::dangling, yanlış iterator kullanımını compile-time’da yakalamaya yönelik bilinçli bir tasarım tercihidir.
std::ranges::enable_borrowed_range
template <typename R>
concept borrowed_range =
ranges::range<R> &&
(std::is_lvalue_reference_v<R> ||
ranges::enable_borrowed_range<std::remove_cvref_t<R>>);
template <typename R>
constexpr bool enable_borrowed_range = false;“T türündeki bir range geçici (temporary) olarak kullanılsa bile ondan elde edilen iterator/sentinel ‘dangling’ olmaz” demenin resmi yoludur.
Ranges algoritmaları bazen bir range’den iterator döndürür (ör. std::ranges::find), ama eğer range temporary ise (örn. make_range() gibi fonksiyonun döndürdüğü geçici nesne) o iterator’un işaret ettiği şey range’in ömrüne bağlıysa, range bittiği anda iterator boşa düşer. C++ bunun için std::ranges::dangling diye bir tür kullanır: “bu iterator güvenli değil” demektir.
Borrowed range fikri şudur:
Bazı range’ler ömrü kendisine bağlı olmayan iteratorlar üretir.
Yani range nesnesi yok olsa bile iteratorlar hâlâ geçerlidir (çünkü asıl veri başka yerde yaşıyordur).
İşte enable_borrowed_range<T> = true; demek:
“T temporary bile olsa, ondan dönen iterator/sentinel güvenli kabul edilebilir” anlamına gelir.
Böylece ranges algoritmaları dangling yerine gerçek iterator döndürebilir.
enable_borrowed_range’i yanlışlıkla true yapmak tehlikelidir. Çünkü bu bir “söz verme”dir: “Bu türden üretilen iteratorlar range’in ömrüne bağlı değil.” Eğer aslında bağlıysa, UB (tanımsız davranış) riskine yol açabilir.
#include <ranges>
#include <cstddef>
struct DataView {
int* p{};
std::size_t n{};
int* begin() const { return p; }
int* end() const { return p + n; }
};
// Bu view bir borrowed range'dir (mantıken güvenli).
template <>
inline constexpr bool std::ranges::enable_borrowed_range<DataView> = true;
DataView make_view(int* p, std::size_t n)
{
return {p, n};
}
int main()
{
int arr[] = {10, 20, 30, 40};
// make_view(...) temporary bir DataView döndürür.
// Ama iteratorlar arr'nin içine pointer olduğu için güvenli.
auto it = std::ranges::find(make_view(arr, 4), 30); // int* döner
}
*****************************
AÇIKLAMA :
temporary bir “view” nesnesi üretiyoruz ama algoritmadan aldığımız iterator, view’e değil, dışarıdaki gerçek veriye (array’e) bağlı oluyor.
Bu yüzden: View ölse bile Iterator ölmüyor; işte “borrowed range” tam olarak bu demektir.
***************************** DataView nedir, ne değildir?
Ne değildir:
Container değildir
Veri sahiplenmez
Memory ayırmaz / free etmez
Nedir:
Sadece başkasına ait bir veriye bakar
İki bilgi tutar:
int* p; // dizinin başlangıcı
std::size_t n; // eleman sayısı
//Yani şuna benzer: std::span<int>begin() ve end() neden pointer?
Iterator = int*
Pointer’lar DataView’e ait değildir
Pointer’lar arr’nin içini gösterir
iterator ───────▶ arr içindeki int
(iterator → DataView’e bağlı değildir)
make_view neden önemlidir?
temporary bir DataView döndürüyor
make_view(arr, 4)
Stack’te geçici bir DataView oluşturur
p = &arr[0], n = 4
std::ranges::find bu view’i kullanıyor
İş bitince DataView yok ediliyor
Ama arr yok edilmez.
View ölünce ne olur?
p ve n üyeleri yok olur ama onların gösterdiği arr hâlâ durur
int* it; demek arr[2] // 30
Yani iterator’un hayatı: arr'nin ömrüne bağlıdır, DataView’in ömrüne bağlı değildir
enable_borrowed_range OLMASAYDI ne olurdu?
auto it = std::ranges::find(make_view(arr, 4), 30); satırı
std::ranges::dangling it; olurdu.
Çünkü Ranges standardı der ki: “Temporary bir range’den iterator döndürüyorsa ve borrowed olduğu bilinmiyorsa, risk alma.”
enable_borrowed_range = true; diyerek :
bu type güvenlidir, bu range temporary olsa bile iteratorlar başka bir şeye (arr’ye) bağlıdır.” deriz.
std::ranges::find gerçek int* döndürür,dangling değildir.
auto it = std::ranges::find(make_view(arr, 4), 30);
*****************************
AÇIKLAMA :
kodunda fonksiyonla geçici bir view üreteyim, ama aradığım elemanın iterator’unu alayım deriz. dangling döndü, kullanamıyorum demek durumunda kalmayalım.
***************************** struct BadRange {
std::vector<int> v;
auto begin() { return v.begin(); }
auto end() { return v.end(); }
};
template <>
inline constexpr bool std::ranges::enable_borrowed_range<BadRange> = true; // hata
auto it = std::ranges::find(BadRange{{1,2,3}}, 2); // U.B.
/*********************************
AÇIKLAMA : Iterator → v’nin içine BadRange ölünce v ölür, Iterator → çöp olur.
*********************************/RULE: A view is a range
Ama her range bir view değildir.
View olmanın anlamı nedir?
Ucuz kopyalanabilen, sahiplenmeyen, sadece başka bir range’e bakan hafif bir range
Yani view olma şartları şunlardır:
Bir T türü :
default constructor
copy constructor
move constructor
destructor
sahip olmalıdır ve
bu işlemlerin tamamı constant time (O(1)) olmalıdır.
Sebep:
View’ler pipeline (|) içinde sürekli kopyalanır / taşınır
Eğer pahalı olsalardı ranges tasarımı çökerdi.
Kritik problem: Derleyici bunu nasıl bilecek?
Derleyici bir sınıfın bu özel üye fonksiyonlarının O(1) olup olmadığını bilemez.
struct Data {
std::vector<int> ivec;
};
/*********************************
AÇIKLAMA : copy ctor var ama O(n) mi, O(1) mi? → derleyici bilemez
*********************************/Çözüm: ranges::enable_view
Bir type view ise, bunu açıkça deklare etmelidir.
std::ranges::enable_view<T>
/*********************************
AÇIKLAMA :
true → bu type bir view
false → view değildir
enable_view bir trait’tir ve default olarak false tanımlıdır.
*********************************/std::vector neden view değildir?
int main()
{
static_assert(std::ranges::enable_view<std::vector<int>>);
}
/*********************************
AÇIKLAMA :
std::vector bir owning container’dır:
-> Belleği sahiplenir
-> Copy ctor O(n)’dir
-> Destructor belleği serbest bırakır
Yani:
-> Hafif değil ve View felsefesine aykırıdır.
-> enable_view<vector<T>> == false
*********************************/std::ranges::owning_view
Ranges + views dünyasında view’ler non-owning olacak şekilde tasarlanmıştır. Ama şu senaryo kaçınılmazdır:
auto get_vec()
{
/*
temporary (prvalue) bir std::vector döndürür
Bu vector owning bir range’dir. Ama biz bunu view pipeline’ına sokmak istiyoruz
*/
return std::vector{ 1, 5, 9 };
}
int foo(int x)
{
return 5;
}
int main()
{
auto vw = get_vec | views::transform(foo);
}
*****************************
AÇIKLAMA : view türü std::ranges::owning_view<...> türündendir.
taşıma semantiği ile view devralıyor gibi düşünebiliriz.
*****************************
CEVAP : legal
*****************************Sorun views::transform, bir view ya da view’e dönüştürülebilecek bir range bekler.
Ancak; get_vec() → temporary vector, vector → view değil, vector → borrowed range değildir.
Eğer bu vector’u olduğu gibi kullanırsak; temporary biter ve transform view dangling olur.
Çözüm: std::ranges::owning_view
Standart kütüphane şunu yapar: Eğer bana temporary bir owning range verirsen, ben onu owning_view içine sararım.
auto vw = std::ranges::owning_view<std::vector<int>>(get_vec()) | views::transform(foo);
// Ama bu dönüşüm otomatik yapılır.owning_view tam olarak ne yapar?
owning_view: view ama owning davranış sergiler.
İçinde şunu tutar: std::vector<int> owned_range;
Yani; underlying range owning_view tarafından sahiplenilir, lifetime artık view’e bağlıdır.
Bu yüzden; pipeline güvenlidir, dangling riski yoktur.
“Taşıma semantiği ile view devralıyor” ne demek?
std::vector<int> tmp = get_vec();
std::ranges::owning_view ov(std::move(tmp));
/***********************************************************
vector taşınır (move), kopyalama yok (O(1)), owning_view bu vector’un yeni sahibi olur. Dolayısıyla: pahalı copy yok, view konsepti bozulmaz
***********************************************************/
Bir range’i view pipeline içinde kullanmak istediğimizde birkaç seçenek vardır:
Range zaten bir view olabilir. Bazı türler doğrudan view’dir; ekstra adaptor gerekmez.
std::span
std::string_view
Bunlar:
non-owning
cheap copy
ranges::view concept’ini doğrudan sağlar
std::views::all kullanılabilir
views::all, herhangi bir range’i uygun bir view türüne dönüştürür.
lvalue → ref_view
rvalue → owning_view
zaten view ise → olduğu gibi döner
std::ranges::subrange kullanılabilir
iterator + sentinel çiftinden bir view oluşturur
bazı adaptorler zaten subrange döndürür
iterator türüne bağlı olarak farklı specialize’lar oluşabilir
int main()
{
vector<int> ivec{ 1, 5, 9, 6 };
auto vw = std::views::all(ivec);
}
*****************************
AÇIKLAMA :
ivec bir lvalue’dur.
views::all, lvalue bir range aldığında:
-> container’ı sahiplenmez, sadece ona referans tutan bir view oluşturur
Ortaya çıkan tür: std::ranges::ref_view<std::vector<int>>
*****************************
CEVAP : legal
*****************************int main()
{
auto vw = views::all(vector<int> ivec{ 1, 5, 9, 6 });
}
*****************************
AÇIKLAMA :
Burada std::vector<int>{...} bir temporary (rvalue)’dır.
views::all bu durumda:
-> dangling riskini önlemek için, container’ı move ederek bir view içine alır.
Ortaya çıkan tür: std::ranges::owning_view<std::vector<int>>
-> Yani; view artık vector’un sahibidir ve lifetime güvenlidir.
*****************************
CEVAP : legal
*****************************std::views::counted
int main()
{
std::vector<int> ivec{ 5, 7, 9, 8, 6 };
auto vw = std::views::counted(ivec.begin(), 5); // bu ifade bir view
}
*****************************
AÇIKLAMA : begin() konumundan başlayarak 3 tane öğe -> 5, 7, 9
vw türü std::span<int, ...>
std::vector iterator’ları: contiguous, pointer benzeri özelliklere sahiptir.
Bu yüzden views::counted: daha özel ve verimli bir view olan std::span üretir
*****************************
CEVAP : legal
*****************************int main()
{
std::list<int> ilist{ 5, 7, 9, 8, 6 };
auto vw = std::views::counted(ilist.begin(), 5); // bu ifade bir view
}
*****************************
AÇIKLAMA : begin() konumundan başlayarak 3 tane öğe -> 5, 7, 9
vw türü std::ranges::subrange<class std::counted_iterator<class std::_List_iterator<class std::_List_val<..., ...>
std::list iterator’ları: contiguous değil, pointer benzeri değildir.
Bu yüzden views::counted: std::span oluşturamaz, daha genel bir çözüm kullanır.
*****************************
CEVAP : legal
*****************************view Kullanmanın Temel Avantajları
Lightweight (hafif) yapılardır→ Algoritmalara call-by-value ile geçirildiğinde kopyalama maliyeti ihmal edilebilir düzeydedir.
Sahiplik içermezler (non-owning)→ Altındaki verinin ömrünü yönetmezler.
Oluşturulmaları constant time’dır (O(1))
Ranges algoritmalarıyla güvenli çalışırlar, borrowed_range kurallarına uydukları sürece.
Lvalue Container + ranges::find
std::vector<int> v;
ranges::find(v, 5);
*****************************
AÇIKLAMA :
-> v bir lvalue range’dir.
-> Lvalue olan tüm range’ler otomatik olarak borrowed_range kabul edilir.
-> ranges::find, v’nin iç belleğini işaret eden bir iterator döndürür.
v hâlâ hayatta olduğu için iterator dangling değildir.
*****************************
CEVAP : güvenli kullanımdır.
*****************************Lvalue std::vector ile açık örnek
int main()
{
std::vector<int> ivec{ 2, 5, 8, 9 };
std::ranges::find(ivec, 6);
}
*****************************
AÇIKLAMA : Lvalue range (kesin borrowed)
- ivec isimli bir nesnedir (lvalue).
- ranges::find iterator döndürür.
- ivec hâlâ hayatta olduğu için iterator dangling olmaz.
- Bu nedenle çağrı tamamen legaldir.
*****************************
CEVAP : legal
*****************************int main()
{
std::string name{ "yusuf" };
auto sv = std::string_view{ name };
auto pos = std::ranges::find(sv, 'y');
if (pos != sv.end())
std::cout << *pos << "\n";
else
std::cout << "not found\n";
}
*****************************
AÇIKLAMA : Rvalue ama borrowed_range: std::string_view
- std::string_view bir view’dır; verinin sahibi değildir.
- string_view{name} name’in iç buffer’ına pointer tutar.
- sv geçici olsa bile, name hâlâ hayatta olduğu için
pos iterator’ı geçerli belleği işaret eder.
- std::string_view, ranges tarafından borrowed_range kabul edilir.
- Bu nedenle rvalue string_view ile iterator döndürmek güvenlidir.
Not:
string_view borrowed_range olsa bile,
işaret ettiği string’in ömrü biterse dangling oluşur.
*****************************
CEVAP : y
*****************************int main()
{
int arr[]{ 1, 2, 3, 4 };
auto pos = std::ranges::find(std::span{ arr }, 3);
std::cout << *pos << "\n";
}
*****************************
AÇIKLAMA :
- std::span bir view’dır ve verinin sahibi değildir.
- span{arr} geçici olsa bile arr dizisi hâlâ hayattadır.
- iterator arr içindeki geçerli belleği işaret eder.
- std::span borrowed_range kabul edilir.
*****************************
CEVAP : 3
*****************************Range adaptors vs View factories
Adaptor (Range adaptor): Bir range alıp (input range) bir view döndüren şeydir. Genellikle pipe ile kullanılır: range | views::filter(pred) veya range | views::reverse.
Factory (View factory): Range almadan bir view üreten fonksiyondur. Örn: views::iota(10), views::repeat('A'), views::single(42).
Not: Bazı view’lar iki şekilde de kullanılabilir
(örn. views::filter(r, pred) ve r | views::filter(pred));
"adaptor closure” dediğimiz pipe-sağında duran nesne adaptordür.
views::filter
1.a — fonksiyon çağrısı formu
int main()
{
std::vector<std::string> svec{ "Mercury","Venus","Earth","Mars","Jupiter",
"Saturn","Uranus","Neptune" };
char c;
std::cout << "icinde hangi karakter olanlar : ";
std::cin >> c;
for (const auto& s : std::views::filter(svec, [c](const auto& s) {
return s.contains(c); // C++23
}))
{
std::cout << s << " ";
}
}
*****************************
AÇIKLAMA : 1.a — fonksiyon çağrısı formu
- views::filter bir range adaptor’dür: verilen range’i “lazy” şekilde filtreleyen bir view üretir.
- Predicate (unary predicate) olarak lambda kullanıldı: [c](const auto& s){...}
Bu lambda “closure type” üretir; c’yi value ile yakalayıp (copy) saklar.
- std::string::contains(c) C++23’tür; s içinde c karakteri varsa true döndürür.
- filter_view elemanları kopyalamaz; svec üzerinde gezen bir görünüm üretir.
*****************************
CEVAP :
Kullanıcının girdiği karaktere göre, o karakteri içeren gezegen isimleri yazdırılır.
Örnek: c='e' girilirse: Mercury Venus Earth Jupiter Neptune
*****************************1.b — pipeline (pipe) formu
int main()
{
namespace vw = std::views;
std::vector<std::string> svec{ "Mercury","Venus","Earth","Mars","Jupiter",
"Saturn","Uranus","Neptune" };
char c;
std::cout << "icinde hangi karakter olanlar : ";
std::cin >> c;
for (const auto& s : svec | vw::filter([c](const auto& s) { return s.contains(c); }))
{
std::cout << s << " ";
}
}
*****************************
AÇIKLAMA : 1.b — pipeline (pipe) formu
- Pipe notation’da sağ taraftaki vw::filter(...) bir “adaptor closure object”tir.
- Sol operand range (svec), sağ operand adaptor olunca yeni bir view oluşur.
- Kod, 1.a ile aynı işi yapar; sadece yazım daha idiomatiktir.
*****************************
CEVAP :
Girdiye bağlı olarak o karakteri içeren string’ler basılır.
*****************************1.c — reverse + filter
int main()
{
namespace vw = std::views;
std::vector<std::string> svec{ "Mercury","Venus","Earth","Mars","Jupiter",
"Saturn","Uranus","Neptune" };
char c;
std::cout << "icinde hangi karakter olanlar : ";
std::cin >> c;
for (const auto& s : svec | vw::reverse | vw::filter([c](const auto& s)
{ return s.contains(c); }))
{
std::cout << s << " ";
}
}
*****************************
AÇIKLAMA : 1.c — reverse + filter
- Pipeline soldan sağa kurulsa da değerlendirme “lazy”dir.
- reverse view: svec’i tersten gezer (kopyalamaz).
- filter view: tersten gelen elemanları predicate’e göre süzer.
- Sonuç: svec’in ters sırası üzerinde “contains(c)” filtresi uygulanır.
*****************************
CEVAP :
Girdi c’ye göre, tersten sırayla o karakteri içerenler yazdırılır.
Örnek: c='u' ⇒ Uranus Saturn Jupiter Venus Mercury (hangi isimlerde 'u' varsa)
*****************************1.d — reverse + take(5) + filter
int main()
{
namespace vw = std::views;
std::vector<std::string> svec{ "Mercury","Venus","Earth","Mars","Jupiter",
"Saturn","Uranus","Neptune" };
char c;
std::cout << "icinde hangi karakter olanlar : ";
std::cin >> c;
for (const auto& s :
svec | vw::reverse | vw::take(5) | vw::filter([c](const auto& s)
{ return s.contains(c); }))
{
std::cout << s << " ";
}
}
*****************************
AÇIKLAMA : 1.d — reverse + take(5) + filter
- reverse: sıra tersten.
- take(5): tersten gelen ilk 5 öğeyi sınırlar (yani orijinalin son 5’i).
- filter: bu 5 öğe içinden c karakterini içerenleri seçer.
- Burada “filter’ı take’dan önce koyarsan” farklı sonuç alırsın:
take önce daraltır, sonra filtreler. (lazy ama mantıksal sıra bu)
*****************************
CEVAP :
Girdi c’ye göre, sadece (Neptune, Uranus, Saturn, Jupiter, Mars) kümesi içinde filtrelenmiş sonuç basılır.
*****************************1.e — std::ranges::filter_view (view type’ı doğrudan kullanmak)
int main()
{
std::vector ivec{ 1, 2, 3, 4, 5, 15, 25, 40, 60 };
std::ranges::filter_view vw{ ivec, [](int x){ return x % 5 == 0; } };
for (auto i : vw)
{
std::cout << i << " ";
}
}
*****************************
AÇIKLAMA : 1.e — std::ranges::filter_view (view type’ı doğrudan kullanmak)
- filter_view bir sınıf türüdür; std::ranges namespace’indedir.
- Bir range + predicate alır ve filtrelenmiş görünüm oluşturur.
- Genellikle doğrudan filter_view yazmak yerine views::filter adaptor’ı tercih edilir.
*****************************
CEVAP : 5 15 25 40 60
*****************************1.f — views::filter(range, pred)
int main()
{
std::vector ivec{ 1, 2, 3, 4, 5, 15, 25, 40, 60 };
auto vw = std::views::filter(ivec, [](int x){ return x % 5 == 0; });
for (auto i : vw)
{
std::cout << i << " ";
}
}
*****************************
AÇIKLAMA : 1.f — views::filter(range, pred)
- views::filter bir adaptor’dür; range verince filter_view üretir.
- vw’nin türü filter_view’dır (auto ile yakalanır).
*****************************
CEVAP : 5 15 25 40 60
*****************************1.g — Pipe ile views::filter
int main()
{
std::vector ivec{ 1, 2, 3, 4, 5, 15, 25, 40, 60 };
auto vw = ivec | std::views::filter([](int x){ return x % 5 == 0; });
for (auto i : vw)
{
std::cout << i << " ";
}
}
*****************************
AÇIKLAMA : 1.g — Pipe ile views::filter
- Pipe kullanımında sol operand range, sağ operand adaptor closure’dır.
- En okunabilir/idiomatik kullanım genellikle budur.
*****************************
CEVAP :
5 15 25 40 60
*****************************1.h — Filtrelenmiş view’dan yeni vector üretmek (C++20/23)
int main()
{
std::vector source{ 1, 4, 7, 8, 6, 2, 5, 9, 4, 12 };
auto vf = source | std::views::filter([](int v){ return v % 2 == 0; });
std::vector<int> dest(vf.begin(), vf.end());
for (auto i : dest)
{
std::cout << i << " ";
}
}
*****************************
AÇIKLAMA : 1.h — Filtrelenmiş view’dan yeni vector üretmek (C++20/23)
- vf bir view’dır; source’u kopyalamaz, filtrelenmiş şekilde gezer.
- vector(range constructor) iteratör çifti ile dest’i doldurur.
- Not: Bazı view’lar “common_range” değildir; begin/end farklı türde olabilir.
Bu örnekte filter_view çoğu zaman common_range olur ve ctor çalışır.
Taşınabilir çözüm için ranges::to veya ranges::copy kullanmak daha güvenlidir.
*****************************
CEVAP : 4 8 6 2 12
*****************************1.i — std::ranges::to<std::vector>() (C++23)
int main()
{
std::vector source{ 1, 4, 7, 8, 6, 2, 5, 9, 4, 12 };
auto dest = source
| std::views::filter([](int v){ return v % 2 == 0; })
| std::ranges::to<std::vector>(); // C++23
for (auto i : dest)
{
std::cout << i << " ";
}
}
*****************************
AÇIKLAMA : 1.i — std::ranges::to<std::vector>() (C++23)
- C++23 std::ranges::to<Container>() adaptor’ı, bir range’i doğrudan container’a “materialize” eder.
- begin/end türlerinin farklı olması gibi durumlarda bile genellikle sorunsuz çalışır.
*****************************
CEVAP : 4 8 6 2 12
*****************************views::repeat (Factory) — C++23
int main()
{
for (auto i : std::views::repeat(5, 7))
std::cout << i;
std::cout << "\n";
for (auto i : std::views::repeat('A') | std::views::take(4))
{
std::cout << i << " ";
}
std::cout << "\n";
}
*****************************
AÇIKLAMA :
- views::repeat bir factory’dir: başlangıçta bir range almaz, kendi view’ını üretir.
- repeat(x, n): x değerini n kez tekrar eden sonlu bir view üretir.
- repeat(x): x’i sonsuz tekrar eden view üretir; bu yüzden take gibi bir adaptor ile sınırlandırmak gerekir.
*****************************
CEVAP :
5555555
AAAA
*****************************views::slide (Adaptor) — C++23
int main()
{
std::vector<std::string> svec{ "Mercury","Venus","Earth","Mars","Jupiter",
"Saturn","Uranus","Neptune" };
std::ranges::copy(svec, std::ostream_iterator<std::string>(std::cout,
" "));
std::cout << "\n\n";
for (auto rn : std::views::slide(svec, 3))
{
for (const auto& s : rn)
{
std::cout << s << " ";
}
std::cout << "\n";
}
}
*****************************
AÇIKLAMA :
- views::slide(range, k) bir adaptor’dür: range üzerinde “k uzunluklu kayan pencereler” üretir.
- Dönen şey: “alt-range’lerden oluşan bir range”dir (range-of-ranges).
- İlk pencerede ilk 3 eleman, sonra birer kayarak devam eder.
*****************************
CEVAP :
Mercury Venus Earth Saturn Uranus Neptune (ilk satır tüm liste; arada boşluklar)
Mercury Venus Earth
Venus Earth Mars
Earth Mars Jupiter
Mars Jupiter Saturn
Jupiter Saturn Uranus
Saturn Uranus Neptune
*****************************views::iota (Factory)
4.a — iota + take
int main()
{
auto v = std::views::iota(10) | std::views::take(5);
for (auto i : v)
{
std::cout << i << " ";
}
}
*****************************
AÇIKLAMA :
- views::iota(10) bir factory’dir: 10’dan başlayıp sonsuza giden sayı akışı üretir.
- take(5) ile sonsuz akışın ilk 5 elemanı alınır.
*****************************
CEVAP : 10 11 12 13 14
*****************************4.b — common_range notu + vector ctor
int main()
{
auto source = std::views::iota(10)
| std::views::take(20)
| std::views::filter([](int v){ return v % 2 == 0; });
std::vector<int> dest(source.begin(), source.end());
}
*****************************
AÇIKLAMA :
- Bazı view zincirleri “common_range” olmayabilir (begin iterator tipi ile end sentinel tipi farklı olabilir).
- Eğer common_range değilse, dest(begin,end) ctor’u derleme hatası verebilir.
- Çözüm: ranges::to veya ranges::copy/back_inserter kullanmak.
Not: Bu örneğin derlenip derlenmemesi implementasyona bağlı olabilir; portable çözüm aşağıdadır.
*****************************
CEVAP :
Taşınabilirlik açısından riskli; bazı ortamlarda derleme hatası oluşabilir.
*****************************4.c — ranges::copy ile portable materialize
int main()
{
auto source = std::views::iota(10)
| std::views::take(20)
| std::views::filter([](int v){ return v % 2 == 0; });
std::vector<int> dest;
std::ranges::copy(source, std::back_inserter(dest));
for (auto i : dest)
{
std::cout << i << " ";
}
}
*****************************
AÇIKLAMA :
- ranges::copy, iterator/sentinel uyumsuzluklarını (common_range olmama) doğal şekilde yönetir.
- back_inserter ile dest’e push_back yaparak doldurur.
- iota(10) -> 10..29, take(20) -> 10..29, filter even -> çiftler.
*****************************
CEVAP :
10 12 14 16 18 20 22 24 26 28
*****************************views::reverse (Adaptor)
int main()
{
std::vector ivec{ 3, 1, -3, 4, 7, 9, -1, 2, 6, 5, 8, 0, -4 };
for (auto i : std::views::reverse(ivec))
{
std::cout << i << " ";
}
std::cout << "\n";
for (auto i : ivec | std::views::reverse)
{
std::cout << i << " ";
}
std::cout << "\n";
std::ranges::reverse_view rv = std::views::reverse(ivec);
for (auto i : rv)
{
std::cout << i << " ";
}
std::cout << "\n";
}
*****************************
AÇIKLAMA :
- reverse bir adaptor’dür: range’i ters sırada “lazy” gezen reverse_view üretir.
- 1. kullanım: views::reverse(range) fonksiyon formu
- 2. kullanım: pipe formu (idiomatik)
- 3. kullanım: reverse_view türünü açıkça yazmak (öğretici)
*****************************
CEVAP :
-4 0 8 5 6 2 -1 9 7 4 -3 1 3
-4 0 8 5 6 2 -1 9 7 4 -3 1 3
-4 0 8 5 6 2 -1 9 7 4 -3 1 3
*****************************views::elements (Adaptor)
6.a — tuple alanlarını ayrı vector’lere toplamak
using mytuple = std::tuple<int, std::string, std::bitset<16>>;
int main()
{
namespace vw = std::views;
std::vector<mytuple> vec {
{ 12, "ali", 567u },
{ 33, "ceyhun", 87234u },
{ 45, "ayse", 192354u },
};
auto ivec = vec | vw::elements<0> | std::ranges::to<std::vector>();// C++23
auto svec = vec | vw::elements<1> | std::ranges::to<std::vector>();
auto bvec = vec | vw::elements<2> | std::ranges::to<std::vector>();
}
*****************************
AÇIKLAMA :
- elements<N> adaptor’ı, tuple/pair gibi “tuple-like” elemanların N’inci alanını projekte eden bir view üretir.
- elements<0> => int’ler, elements<1> => string’ler, elements<2> => bitset’ler
- ranges::to<std::vector>() (C++23) ile bu view’ları gerçek vector’e dönüştürdük.
*****************************
CEVAP :
ivec = {12,33,45}
svec = {"ali","ceyhun","ayse"}
bvec = {567u,87234u,192354u} (bitset olarak)
*****************************6.b — elements<0> ile dolaşmak
using mytuple = std::tuple<int, std::string, std::bitset<16>>;
int main()
{
namespace vw = std::views;
std::vector<mytuple> vec {
{ 12, "ali", 567u },
{ 33, "ceyhun", 87234u },
{ 45, "zeynep", 192354u },
};
auto v = vec | vw::elements<0>;
for (auto i : v)
{
std::cout << i << " ";
}
}
*****************************
AÇIKLAMA : 6.b — elements<0> ile dolaşmak
- v bir view’dır; vec’in elemanlarını kopyalamaz.
- Her tuple’dan 0. elemanı (int) “projection” gibi verir.
*****************************
CEVAP :
12 33 45
*****************************6.c — reverse + elements<0>
using mytuple = std::tuple<int, std::string, std::bitset<16>>;
int main()
{
namespace vw = std::views;
std::vector<mytuple> vec {
{ 12, "ali", 567u },
{ 33, "ceyhun", 87234u },
{ 45, "zeynep", 192354u },
};
auto v = vec | vw::reverse | vw::elements<0>;
for (auto i : v)
{
std::cout << i << " ";
}
}
*****************************
AÇIKLAMA : 6.c — reverse + elements<0>
- Pipeline sırası: önce ters sırada gez, sonra her elemandan 0. alanı seç.
- Sonuç: int’ler ters sırada gelir.
*****************************
CEVAP : 45 33 12
*****************************views::keys (Adaptor) — pair/tuple-like için
int main()
{
std::vector<std::pair<std::string, int>> vec;
for (int i{}; i < 10; ++i)
{
vec.emplace_back(rname(), rand());
}
for (const auto& s : std::views::keys(vec))
{
std::cout << s << " ";
}
}
*****************************
AÇIKLAMA :
- keys adaptor’ı, pair/tuple-like elemanların “0. elemanını” (key) projekte eder.
- std::views::keys(vec) ≈ vec | views::elements<0>
- vec’teki string’leri (isimleri) basar.
*****************************
CEVAP :
Rastgele 10 isim (sırası vec’e eklenme sırası).
*****************************views::values (Adaptor)
int main()
{
std::vector<std::pair<std::string, int>> vec;
for (int i{}; i < 10; ++i)
vec.emplace_back(rname(), rand());
for (const auto& x : std::views::values(vec))
{
std::cout << x << " ";
}
}
*****************************
AÇIKLAMA :
- values adaptor’ı, pair/tuple-like elemanların “1. elemanını” (value) projekte eder.
- Yani burada random int değerlerini basar.
*****************************
CEVAP :
Rastgele 10 tamsayı (rand() çıktıları).
*****************************views::zip (Factory/Adaptor) — (C++23/26 duruma bağlı)
int main()
{
std::vector<std::pair<std::string, int>> vec;
for (int i{}; i < 10; ++i)
vec.emplace_back(rname(), rand());
auto vw = std::views::zip(std::views::values(vec), std::views::keys(vec));
for (auto [no, name] : vw)
std::cout << no << " " << name << "\n";
}
*****************************
AÇIKLAMA :
- zip, iki range’i “paralel” yürüyüp her adımda bir tuple/pair üretir.
- Burada bilinçli olarak values(vec) ile keys(vec) yer değiştirdi:
[no, name] bağlaması için zip(values, keys) yapıldı.
- Çıktı: her satırda “int sonra string”.
Not:
views::zip standarda daha yeni girdi; bazı derleyici/STL sürümlerinde henüz yok olabilir.
*****************************
CEVAP :
10 satır: "<random_int> <random_name>"
*****************************views::split (Adaptor) + drop_while/reverse/drop zinciri
10.a — split örneği
int main()
{
namespace vw = std::views;
std::vector ivec{ 2, 5, 1, 4, 1, 2, 9, 8, 7, 1, 5, 6 };
auto rg = ivec | vw::split(1);
for (auto sub : rg)
{
for (auto i : sub)
{
std::cout << i << " ";
}
std::cout << "\n";
}
}
*****************************
AÇIKLAMA :
- split(1) adaptoru, eleman değeri 1’i “ayraç” kabul edip range’i parçalara böler.
- Dönen şey “subrange/view’lardan oluşan bir range”dir (range-of-ranges).
- Ayraç olan 1’ler çıktıya dahil edilmez.
*****************************
CEVAP :
2 5
4
2 9 8 7
5 6
*****************************10.b — drop_while + reverse + drop
int main()
{
namespace vw = std::views;
std::vector pvec{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 };
for (auto i : pvec
| vw::drop_while([](int x){ return x < 10; })
| vw::reverse
| vw::drop(3))
{
std::cout << i << " ";
}
}
*****************************
AÇIKLAMA :
Adım adım:
1) drop_while(x < 10):
Baştan başlayarak 10’dan küçükler (2,3,5,7) “düşürülür”.
Kalan: 11 13 17 19 23 29
2) reverse:
29 23 19 17 13 11
3) drop(3):
İlk 3 elemanı atar (29,23,19 gider)
Kalan: 17 13 11
Not: Senin açıklamada “12” yazıyordu; doğrusu 11’dir.
*****************************
CEVAP :
17 13 11
*****************************10.c — Aynı zinciri listeye materialize etmek (C++23 ranges::to ile)
int main()
{
namespace vw = std::views;
std::vector pvec{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 };
auto ilist = pvec
| vw::drop_while([](int x){ return x < 10; })
| vw::reverse
| vw::drop(3)
| std::ranges::to<std::list<int>>(); // C++23
for (auto i : ilist)
{
std::cout << i << " ";
}
}
*****************************
AÇIKLAMA :
- Önceki örnekteki view zinciri aynen kurulur (lazy).
- ranges::to<std::list<int>>() zincirin çıktısını gerçek bir list’e dönüştürür (materialize eder).
- Böylece artık view değil, owning bir container elde etmiş oluruz.
*****************************
CEVAP :
17 13 11
*****************************“View’lar Lazy’dir: Tekrar iterasyon, caching, invalidation ve lifetime tuzakları”
Lazy evaluation & caching (filter_view)
Underlying range değişince view ne olur? (drop_view + list vs vector)
Const iteration / concept kısıtları (drop_view on list, filter predicate const)
Lifetime: View döndürmek (dangling riskleri) / owning_view
namespace vw = std::views;
int main ()
{
std::vector ivec{ 2, 7, 9, 5, 4, 10, 6, 7 };
auto v = ivec | vw::filter([](int x){
std::cout << "x : " << x << "\n";
return x % 5 == 0;
});
std::cout << "first tour\n";
for (auto i : v)
{
(void)i;
}
std::cout << "\nsecond tour\n";
for (auto i : v)
{
(void)i;
}
}
*****************************
AÇIKLAMA :
- filter_view “lazy”dir: predicate, elemanlar iterasyon sırasında test edilir.
- Bazı view’lar (özellikle forward_range üzerinde) performans için “başlangıç konumunu” cache’leyebilir.
Yani ilk uygun elemanı (burada 5) bulduktan sonra, bir sonraki iterasyonda tekrar baştan taramak yerine
cached begin konumundan başlayabilir.
- Bu yüzden 2. turda predicate’in bazı elemanlar için yeniden çağrılmadığını görürsün.
- Bu bir “runtime error/UB” değildir; view’ın optimize davranışıdır.
Çıktıdaki “x:” logu predicate çağrılarını gösterdiği için fark görünür.
Not:
- İlk uygun eleman 5’tir. İkinci turda çoğu implementasyon 5’in konumunu cache’lediği için
2,7,9 tekrar test edilmez; bazı implementasyonlar 5’i de tekrar etmeyebilir.
*****************************
CEVAP :
first tour
x : 2
x : 7
x : 9
x : 5
x : 4
x : 10
x : 6
x : 7
second tour
x : 5
x : 4
x : 10
x : 6
x : 7
(İmplementasyona göre ikinci turda “x : 5” satırı da çıkmayabilir.
Önemli olan: 2,7,9’un artık test edilmemesi cache davranışını gösterir.)
*****************************void pr_range(std::ranges::input_range auto&& rng)
{
for (const auto& val : rng)
std::cout << val << " ";
std::cout << "\n";
}
int main ()
{
std::list ilist{ 2, 3, 5, 7, 11, 13 };
auto v = ilist | std::views::drop(3);
pr_range(v);
}
*****************************
AÇIKLAMA :
- drop(n) adaptoru range’in ilk n elemanını atlayan (skip) bir view üretir.
- list üzerinde drop(3) => 2,3,5 atılır; kalan: 7,11,13
- drop_view eleman kopyalamaz; ilist üzerinde gezer.
*****************************
CEVAP :
7 11 13
*****************************int main ()
{
std::list ilist{ 2, 3, 5, 7, 11, 13 };
auto v = ilist | std::views::drop(3);
ilist.push_front(-1);
pr_range(v);
}
*****************************
AÇIKLAMA :
- drop_view, underlying range’in “anlık durumuna göre” iterasyon yapar.
- list’te push_front iterator invalidation yapmaz (list node tabanlıdır).
- Ancak drop(3) “ilk 3 elemanı atla” demektir; liste başa -1 eklenince,
artık ilk 3 eleman: -1,2,3 olur.
- Bu nedenle drop sonrası başlayan eleman artık 5 olur.
- Bu UB değildir; sadece “view, underlying veriye bağlıdır” mantığının sonucu.
*****************************
CEVAP :
5 7 11 13
*****************************int main ()
{
std::list ilist{ 2, 3, 5, 7, 11, 13 };
auto v = ilist | std::views::drop(3);
pr_range(v);
ilist.push_front(-1);
pr_range(v);
}
*****************************
AÇIKLAMA :
- İlk pr_range(v) çağrısında view, “o anki listeye göre” drop(3) uygular:
2,3,5 atılır -> 7 11 13 basılır.
- Sonra liste değişir (başa -1 eklendi). Bu, view’ı bozmuyor (list iteratorları invalid olmaz).
- İkinci pr_range(v) artık yeni listeye göre drop(3) uygular ve 5’ten başlar:
(-1,2,3 atılır) -> 5 7 11 13 basılır.
- Yani UB yok; fakat “aynı view aynı sonucu verir” varsayımı yanlış olabilir.
View’lar owning değilse, underlying değişince sonuç değişebilir.
*****************************
CEVAP :
7 11 13
5 7 11 13
*****************************int main ()
{
std::vector ivec{ 2, 3, 5, 1, 2, 8, 7 };
auto bt3 = [](int x){ return x > 3; };
auto v = ivec | std::views::filter(bt3);
++ivec[1]; // 3 -> 4
ivec[2] = 0; // 5 -> 0
pr_range(v);
}
*****************************
AÇIKLAMA :
- filter_view “görünüm”dür; ivec’i kopyalamaz, her iterasyonda güncel değerlere bakar.
- Değişikliklerden sonra ivec: {2,4,0,1,2,8,7}
- Predicate x>3 koşulunu sağlayanlar: 4,8,7
- Bu tür “mutasyon sonrası view sonucu” UB değildir; fakat
bazı view’larda caching varsa farklı sürprizler doğabilir (özellikle filter’da).
Yine de burada sonuç mantıksal olarak güncel veriyle hesaplanır.
*****************************
CEVAP :
4 8 7
*****************************int main ()
{
namespace vw = std::views;
std::vector ivec{ 1,2,3,4,5,6,7,8,9 };
std::list ilist{ 1,2,3,4,5,6,7,8,9 };
print(ivec | vw::take(3)); // OK
print(ivec | vw::drop(3)); // OK
print(ilist | vw::take(3)); // OK
print(ilist | vw::drop(3)); // Genelde OK; ancak bazı implementasyonlarda const-iterability kısıtları görülebilir.
auto is_even = [](int v) { return v % 2 == 0; };
print(ivec | vw::filter(is_even)); // OK (predicate const-callable)
}
*****************************
AÇIKLAMA :
- Buradaki ana mesaj: “Her view her koşulda const-range gibi davranmaz.”
Bazı view’ların begin() fonksiyonu const overload’a sahip olmayabilir veya
underlying range’in kategorisine göre kısıtlı olabilir.
- filter için predicate’in const-callable olması önemlidir:
View iteratörü predicate’i genellikle const context’te çağırır. Lambda default olarak const-callable’dır.
- drop_view, take_view gibi adaptörler underlying range’e bağlı concept’ler ister.
(forward_range, sized_range, common_range gibi)
Not:
- Senin orijinal notundaki “ilist | drop(3) ERROR” ifadesi,
belirli bir derleyici/STL kombinasyonunda yaşanmış olabilir ama genelleme olarak “her zaman error” denmez.
Makalede “bazı implementasyonlarda/ bazı range kategorilerinde const iteration kısıtları çıkabilir” diye yazmak doğru.
*****************************
CEVAP :
take(3) → 1 2 3
drop(3) → 4 5 6 7 8 9
filter even → 2 4 6 8
*****************************auto get_elem()
{
std::vector ivec{ 3, 6, 9, 45 };
return ivec | std::views::take(4);
}
int main()
{
auto v = get_elem();
for (auto i : v) // UB
std::cout << i << " ";
}
*****************************
AÇIKLAMA :
- take_view owning değildir; underlying range’e referans/iterator tutar.
- ivec, get_elem() sonunda yok olur.
- Dönen view artık yok olmuş vektörün belleğini işaret eder → dangling → UB.
- Bu, “local değişken referansı döndürmek” ile aynı sınıfta bir hatadır.
*****************************
CEVAP :
Tanımsız davranış (çökebilir, çöp değer basabilir, bazen “doğru gibi” görünebilir).
*****************************auto get_elem()
{
return std::vector{ 3, 6, 9, 45 } | std::views::take(4);
}
int main()
{
auto v = get_elem();
for (auto i : v)
std::cout << i << " ";
}
*****************************
AÇIKLAMA :
- Burada sol taraftaki std::vector geçici (rvalue) olduğu için,
views pipeline onu güvenli tutmak adına bir owning_view benzeri mekanizmayla “sahiplenebilir”.
- Böylece view’ın ömrü boyunca vektör verisi de yaşar.
- Sonuç: dangling olmaz, kullanım legaldir.
Not:
- Bu davranış “range adaptor closure”ların rvalue range’leri güvenle taşıyabilmesi (owning_view) tasarımına dayanır.
*****************************
CEVAP : 3 6 9 45
*****************************auto get_elem()
{
std::vector ivec{ 3, 6, 9, 45 };
return std::move(ivec) | std::views::take(4);
}
int main()
{
auto v = get_elem();
for (auto i : v)
std::cout << i << " ";
}
*****************************
AÇIKLAMA :
- ivec’i std::move ile rvalue yaptık.
- Pipeline rvalue range’i “owning” biçimde taşır (view artık verinin sahibi/taşıyıcısı olur).
- Böylece get_elem bitince bile veri view ile birlikte yaşamaya devam eder.
*****************************
CEVAP : 3 6 9 45
*****************************




Comments