> "ranges" kütüphanes: Eski STL kütüphanesinin 2.0 versiyonudur diyebiliriz. Tavsiye edilen, aksi yönde karar almamızı gerektiren bir senaryo olmadığı müddetçe bu 2.0 versiyonunu kullanmamızdır. Çünkü yenisini kullanmak; -> Daha pratik. -> Daha güvenli. -> Daha verimli. -> İlave avantajlar kazandırıyor. Fakat yenisini kullanabilmek için de öğrenmemiz gerekmektedir. Bu kütüphane ile "ranges" kütüphanesinin farkları ise şu şekildedir: -> "ranges" ile gelen algoritmalar, "constraint" edilmişlerdir. -> "ranges" ile gelen algoritmalar ile "STL" içerisindeki algoritmaların parametrik yapılarında da farklılık bulunmaktadır. Yani "STL" içerisindeki algoritmalar takribi şu şekildedir: template void algo(Iter begin, Iter end); Fakat "ranges" ile gelen algoritmalar ise artık şu şekildedir: template void algo(Iter begin, Sentinel end); Buradan da görüleceği üzere "ranges" algoritma fonksiyonlarına geçilen argümanlar farklı türden olabilirler. Anektod: "ranges" içerisindeki algoritma fonksiyonlarına geçilen iş bu argümanlar aynı türden ise, "common-range" ismi ile nitelenirler. -> Diğer yandan "ranges" ve STL kütüphanelerindeki benzer isimdeki algoritma fonksiyonlarının büyük çoğunluğunun geri dönüş değerleri farklılık göstermektedir. Artık "ranges" kütüphanesindeki versiyonlar, daha fazla bilgi içermesi hasebiyle, başka türden değerler döndürmektedir. -> Öte yandan "ranges" ile gelen algoritmalar "projection" denilen önemli bir niteliğe de sahiptirler.Pekiyi nedir bu "projection" mevzusu? Konuyu incelemek adına "find" fonksiyonunun STL içerisindeki versiyonunu inceleyelim. Anımsanacağı üzere aşağıdaki gibi implemente edilmektedir: template InIter Find(InIter beg, InIter end, const T& value) { while (beg != end) { if (*beg == value) return beg; ++beg; } return end; } Fakat şimdide ilgili fonksiyonun "Projection" isminde bir şablon parametresine ve ilgili fonksiyonun da bu türden bir parametreye sahip olduğunu, yani aşağıdaki gibi implemente edildiğini, varsayalım: template InIter Find(InIter beg, InIter end, const T& value, Projection pr) { while (beg != end) { if (pr(*beg) == value) return beg; ++beg; } return end; } Artık "*beg" değeri yerine, "pr(*beg)" çağrısı ile elde edilen değer kullanılacaktır. Fakat bu şekilde bırakılırsa, "pr" nin bir "data member pointer" olması durumunda sorun çıkacaktır. İşte bu sorunu gidermek adına "std::invoke" çağrısını kullanabiliriz. Şöyleki: template InIter Find(InIter beg, InIter end, const T& value, Projection pr) { while (beg != end) { if (std::invoke(pr, *beg) == value) return beg; ++beg; } return end; } Artık "Find" fonksiyonunun üçüncü parametresine hem bir "callable" hem de "data member pointer" geçebiliriz. Şimdi karşımıza şöyle bir sorun çıkmıştır: artık "Find" fonksiyonu için üçüncü bir argüman GEÇMEK ZORUNDAYIZ. İşte bu problemi de giderebilmek adına o parametre için varsayılan argüman belirtmeliyiz. Bunun için "std::identity" türünü kullanabiliriz. Bu tür takribi olarak aşağıdaki biçimde implemente edilmiştir: struct Identity { template T operator()(T x) const { return x; } }; Görüleceği üzere argüman olarak aldığı ifadeyi geri döndürmektedir. İşte bu türü de "Find" fonksiyonumuzda aşağıdaki gibi kullanabiliriz: struct Identity { template T operator()(T x) const { return x; } }; template InIter Find(InIter beg, InIter end, const T& value, Projection pr = {}) { while (beg != end) { if (std::invoke(pr, *beg) == value) return beg; ++beg; } return end; } Artık "Find" fonksiyonunun üçüncü parametresine argüman geçilmediğinde, "Identity" türü kullanılacak. Şimdi de bu kütüphanedeki fonksiyonları tanıyabileceğimiz temel örneklere bakalım: * Örnek 1, #include "MyUtility.h" #include #include #include int main() { /* # OUTPUT # 03 Subat 1999 Carsamba 17 Aralik 2009 Persembe 20 Agustos 1993 Cuma 03 Subat 1999 Carsamba 17 Aralik 2009 Persembe 20 Agustos 1993 Cuma */ using namespace MyUtility; std::vector dvec; Utility::rfill(dvec, 3, Date::Date::random); std::sort(dvec.begin(), dvec.end()); std::copy(dvec.begin(), dvec.end(), std::ostream_iterator{std::cout, "\n"}); puts(""); std::ranges::sort(dvec); std::ranges::copy(dvec, std::ostream_iterator{std::cout, "\n"}); } * Örnek 2, #include #include #include #include int main() { std::list iList; for(auto i{0}; i < 5; ++i) iList.push_back(i); std::ranges::sort(iList); // ERROR: note: constraints not satisfied // "Random Access Iterator" olması gerekmektedir. return 0; } * Örnek 3, #include #include #include #include /* * "ranges" is a namespace. * "range" is the name of a concept. */ // WAY - I template void algo_i(T&& coll) {} // WAY - II void algo_ii(std::ranges::range auto&&) {} /* * So, "MyRange" is just like a "range". */ template concept MyRange = requires(T& t){ std::ranges::begin(t); std::ranges::end(t); }; // WAY - I template void MyAlgo_i(T&& coll) {} // WAY - II void MyAlgo_ii(std::ranges::range auto&&) {} int main() { { algo_i(12); // ERROR: constraints not satisfied int a[10]; algo_i(a); // OK algo_i(std::string{"Merve"}); // OK algo_i(std::string_view{"Menekse"}); // OK } puts("\n"); { algo_ii(12); // ERROR: constraints not satisfied int a[10]; algo_ii(a); // OK algo_ii(std::string{"Merve"}); // OK algo_ii(std::string_view{"Menekse"}); // OK } puts("\n"); { MyAlgo_i(12); // ERROR: constraints not satisfied int a[10]; MyAlgo_i(a); // OK MyAlgo_i(std::string{"Merve"}); // OK MyAlgo_i(std::string_view{"Menekse"}); // OK } puts("\n"); { MyAlgo_ii(12); // ERROR: constraints not satisfied int a[10]; MyAlgo_ii(a); // OK MyAlgo_ii(std::string{"Merve"}); // OK MyAlgo_ii(std::string_view{"Menekse"}); // OK } return 0; } * Örnek 4.0, #include #include #include #include #include #include void algo(std::ranges::range auto) { std::cout << "range\n"; } void algo(std::ranges::input_range auto) { std::cout << "input_range\n"; } // Subsumes above range concept. void algo(std::ranges::forward_range auto) { std::cout << "forward_range\n"; } // Subsumes above range concept. // Subsumes above range concept. void algo(std::ranges::bidirectional_range auto) { std::cout << "bidirectional_range\n"; } // Subsumes above range concept. void algo(std::ranges::random_access_range auto) { std::cout << "random_access_range\n"; } // Subsumes above range concept. void algo(std::ranges::contiguous_range auto) { std::cout << "contiguous_range\n"; } int main() { algo(std::vector{}); // contiguous_range algo(std::deque{}); // random_access_range algo(std::list{}); // bidirectional_range algo(std::forward_list{}); // forward_range return 0; } * Örnek 4.1, #include #include #include void algo(std::ranges::range auto) { std::cout << "range\n"; } void algo(std::ranges::sized_range auto) { std::cout << "sized_range\n"; } // Derleme zamanında çağrılabilir ".size()" fonksiyonu // bulunması gerekmektedir. "std::forward_list" bu şartı // sağlamamaktadır. Ek olarak sadece ve sadece "range" // isimli konsepti "subsume" ETMEKTEDİR. int main() { algo(std::vector{}); // sized_range return 0; } * Örnek 5, #include #include #include #include #include #include #include "MyUtility.h" int main() { /* # OUTPUT # 100 65 35 -34 -9 -59 -32 -78 -68 72 8 97 48 -42 79 -23 -56 -98 18 84 ----------------------------------------------------------------------------- -98 -78 -68 -59 -56 -42 -34 -32 -23 -9 8 18 35 48 65 72 79 84 97 100 ----------------------------------------------------------------------------- 100 97 84 79 72 65 48 35 18 8 -9 -23 -32 -34 -42 -56 -59 -68 -78 -98 ----------------------------------------------------------------------------- 8 -9 18 -23 -32 -34 35 -42 48 -56 -59 65 -68 72 -78 79 84 97 -98 100 ----------------------------------------------------------------------------- 8 -9 18 -23 -32 -34 35 -42 48 -56 -59 65 -68 72 -78 79 84 97 -98 100 ----------------------------------------------------------------------------- */ std::vector ivec; MyUtility::Utility::rfill(ivec, 20, MyUtility::Random::Irand{-100, 100}); MyUtility::Utility::print(ivec); // "std::ranges::sort" w/ default arguments: std::ranges::sort(ivec); MyUtility::Utility::print(ivec); // "std::ranges::sort" w/ a custom argument instead of "std::less", // but a default argument for the Projection param: std::ranges::sort(ivec, [](int a, int b) { return a > b; }); MyUtility::Utility::print(ivec); // "std::ranges::sort" with "std::less" as a default and a lambda expression for the Projection param: std::ranges::sort(ivec, {}, [](int a) { return std::abs(a); }); MyUtility::Utility::print(ivec); // "std::ranges::sort" w/ a custom argument instead of "std::less", // but a default argument for the Projection param: std::ranges::sort(ivec, [](int a, int b) { return std::abs(a) < std::abs(b); }); MyUtility::Utility::print(ivec); } * Örnek 6, #include #include #include #include #include #include #include "MyUtility.h" struct Point { Point() = default; Point(int a, int b) : m_a{a}, m_b{b} {} int m_a{}; int m_b{}; friend std::ostream& operator<<(std::ostream& os, const Point& p) { return os << "[" << p.m_a << "," << p.m_b << "]"; } }; Point create_random_point() { MyUtility::Random::Irand my_rand{0, 99}; return Point{ my_rand(), my_rand() }; } int main() { /* # OUTPUT # [1,2] [7,49] [9,46] [15,0] [15,69] [16,47] [17,25] [31,12] [38,60] [39,28] [44,89] [56,86] [58,59] [60,57] [67,61] [69,16] [70,67] [82,79] [85,53] [88,11] */ std::vector pvec(20); std::ranges::generate(pvec, create_random_point); std::ranges::sort(pvec, {}, &Point::m_a); // Burada yapılacak olan karşılaştırma, ilgili "Point" // nesnelerinin "m_a" veri elemanlarına göre yapılacaktır. // Tabii burada "Projection" yerine ikinci parametreye "custom" // bir karşılaştırma fonksiyonu yazarak veya ikinci ve üçüncü // parametreler için varsayılan argümanı kullanmak adına, "Point" // sınıfımıza bir "küçüktür operator fonksiyonu" da yazabilirdik. std::ranges::copy(pvec, std::ostream_iterator{std::cout, "\n"}); } * Örnek 7, #include #include #include #include #include "MyUtility.h" int main() { /* # OUTPUT # tevfik poturgeli sabriye aksakal nihat sefiloglu mert cubukay bilal silahdar sadegul tepecik dilber uslu deniz mertek ceyhun ormanci taci issiz necmettin yilgin recep soysalan sidre unalmis sezen erim nalan kesman ata beyaz murat yelden niyazi simsek samet alemdar cezmi kurban Found => sabriye aksakal */ std::vector svec; MyUtility::Utility::rfill( svec, 20, [] { return MyUtility::Utility::rname() + ' ' + MyUtility::Utility::rfname(); } ); std::ranges::copy(svec, std::ostream_iterator{std::cout, "\n"}); // Artık iteratör konumundaki nesne değil, o nesnenin uzunluğunu "15" ile karşılaştıracak. if (auto iter = std::ranges::find(svec, 15, [](const std::string& s) { return s.size(); }); iter != svec.end()) std::cout << "Found => " << *iter << '\n'; else std::cout << "Not found\n"; } * Örnek 8, Aşağıdaki örnekte ise "ranges" parametreli "overload" çağrıldığında, "iterator" parametreli fonksiyona çağrı yapmaktadır. Böylelikle "ranges" parametreleri bir nevi "iterator" parametresi olarak kullanmış olduk. template< std::input_iterator Iter, std::sentinel_for SenType, typename Init = std::iter_value_t, typename Op = std::plus<>, typename Proj = std::identity > Init Accumulate(Iter beg, SenType end, Init init = Init{}, Op op = {}, Proj proj = {}) { while(beg != end){ init = std::invoke(op, std::move(init), std::invoke(proj, *beg)); ++beg; } return init; } template< std::ranges::input_rage R, class Init = std::ranges::range_value_t, typename Op = std::plus<>, typename Proj = std::identity > Init Accumulate(R&& r, Init init = Init{}, Op op = {}, Proj proj = {}) { return Accumulate(std::ranges::begin(r), std::ranges::end(r), std::move(init), std::move(op), std::move(proj)); } int main() { //... } Şu da unutulmamalıdır ki; "range" LER BİR TÜR BELİRTMEZLER, BİR "concept" TİRLER. Yani "range concept" ini "satisfy" eden varlıklara "range" denmektedir. Anımsanacağı üzere, yukarıdaki örnekte kullandığımız ve "ranges" isim alanı içerisinde bulunan "sort" gibi işlevler aslında bir fonksiyon değildir. Fakat eski STL fonksiyonları birer şablonlardı. Artık yeni STL'dekiler bir sınıf türünden nesnelerdir. "sort()" ifadesiyle de bizler, o sınıfın ".operator()()" fonksiyonuna çağrı yapmaktayız. Yani aslında ".operator()()" fonksiyonları şablon halindedir. Pekiyi arkadaki esas sınıf nedir? * Örnek 1, #include #include #include int main() { auto name{ typeid(std::ranges::sort).name() }; std::cout << name << ' '; // class std::ranges::_Sort_fn } * Örnek 2, Possible Implementation taken by "https://en.cppreference.com/w/cpp/algorithm/ranges/sort". struct sort_fn { template< std::random_access_iterator I, std::sentinel_for S, class Comp = ranges::less, class Proj = std::identity > requires std::sortable< I, Comp, Proj > constexpr I operator()(I first, S last, Comp comp = {}, Proj proj = {}) const { if (first == last) return first; I last_iter = ranges::next(first, last); ranges::make_heap(first, last_iter, std::ref(comp), std::ref(proj)); ranges::sort_heap(first, last_iter, std::ref(comp), std::ref(proj)); return last_iter; } template< ranges::random_access_range R, class Comp = ranges::less, class Proj = std::identity > requires std::sortable< ranges::iterator_t, Comp, Proj > constexpr ranges::borrowed_iterator_t< R > operator()(R&& r, Comp comp = {}, Proj proj = {}) const { return (*this)(ranges::begin(r), ranges::end(r), std::move(comp), std::move(proj)); } }; inline constexpr sort_fn sort {}; Pekiyi neden böyle bir şey yapılmış? Anımsanacağı üzere "ADL" mekanizmasının devreye girmesi için çağrılan şeyin doğrudan fonksiyon olması gerekmektedir. Bir "function object" vasıtasıyla yapılan fonksiyon çağrılarında "ADL" devreye GİRMEYECEKTİR. Böylelikle isim arama ile ilgili bazı problemler de ortadan kaldırılmış olur. Özetle bizim yeni algoritmalarımız doğrudan fonksiyon DEĞİLLERDİR. Şimdi de "view" kavramı üzerinde duralım. >> "view" kavramı: "ranges" kütüphanesinin asıl önemli avantajlarından birisi, fonksiyonel programlama paradigmasına uyum göstermesini sağlayan, bünyesinde barındırdığı "view" kavramıdır. "view" da bir "range" olarak geçmektedir fakat hafif siklet bir "range". "range" yerine "view" oluşturduğumuzda; -> Sabit zamanda, "in constant time", kopyalanma ya da taşınma garantisi elde ediyoruz. -> "view" konseptini ve başka diğer konseptini karşılayan varlıklar, özel bazı işlemlerde kullanılabiliyorlar. Örneğin, "range-based for-loop" da kullanılabiliyorlar. Böylece bir "range" den hareketle bir "view" oluşturabiliyor, bu "view" ları fonksiyonlara argüman olarak geçebiliyor, bu "view" ları "range" ler ile ya da başka "view" lar ile birlikte bir "Composition" ilişkisi içerisinde kullanabiliyorum. Yine arka planda "view" lar da birer "function object" tir ve "Range Adaptors" dediğimiz varlıklar, bize, fonksiyon çağrı operatörleri ile "view" konseptini destekleyen öğeler döndürüyorlar. Yani bir "view" oluşturmanın yolu ya doğrudan o "view" türünden bir nesne oluşturmak ya da "function object" kullanarak fonksiyon çağrı operatörü ile ilgili "view" ı elde etmektir. Bu "view" kavramı içerisindeki varlıklar, "std::ranges" içerisindeki "view" isim alanı içerisindedir. Bir "range" in "view" niteliğinde olması için, "ranges::view" konseptini desteklemesi gerekmektedir. * Örnek 1, #include #include #include #include "MyUtility.h" int main() { /* # OUTPUT # 41 946 347 714 695 172 222 791 705 669 394 401 490 7 367 371 619 846 440 273 ----------------------------------------------------------------------------- 41 946 347 714 695 172 222 791 705 669 695 705 490 440 */ std::vector ivec; MyUtility::Utility::rfill(ivec, 20, MyUtility::Random::Irand{0, 999}); MyUtility::Utility::print(ivec); // namespace allias kullanıldığı için bu şekilde yazılmıştır. for(auto i : std::views::take(ivec, 10)){ // Buradaki "range-based for loop", "std::views::take(ivec, 10)" // ifadesi ile elde edilen "range" i dolaşacaktır. Bu "range" ise // "ivec" içerisindeki ilk on öğeden meydana gelmektedir. std::cout << i << ' '; } puts("\n"); // namespace allias kullanıldığı için bu şekilde yazılmıştır: for(auto i : std::views::filter(ivec, [](int x){ return x%5==0; })){ // Buradaki "range-based for loop", "std::views::filter(ivec, [](int x){ return x%5==0; })" // ifadesi ile elde edilen "range" i dolaşacaktır. Bu "range" ise // "ivec" içerisindeki elemanlardan beşe tam bölünenlerden // oluşmaktadır. std::cout << i << ' '; } } * Örnek 2, #include #include #include #include #include "MyUtility.h" int main() { /* # OUTPUT # 511 407 807 315 781 143 366 161 899 196 925 71 990 323 557 76 957 645 514 40 ----------------------------------------------------------------------------- 511 407 807 315 781 143 366 161 899 196 511 407 807 315 781 143 366 161 899 196 511 407 807 315 781 143 366 161 899 196 511 407 807 315 781 143 366 161 899 196 511 407 807 315 781 143 366 161 899 196 511 407 807 315 781 143 366 161 899 196 */ std::vector ivec; MyUtility::Utility::rfill(ivec, 20, MyUtility::Random::Irand{0, 999}); MyUtility::Utility::print(ivec); for(auto i : /* std::views::take(ivec, 10) */ ivec | std::views::take(10)) std::cout << i << ' '; puts(""); for(auto i : std::ranges::take_view(ivec, 10)) std::cout << i << ' '; puts("\n"); std::ranges::for_each( std::views::take(ivec, 10), [](int x){ std::cout << x << ' '; } ); puts(""); std::ranges::for_each( std::ranges::take_view(ivec, 10), [](int x){ std::cout << x << ' '; } ); puts("\n"); auto vw = std::views::take(ivec, 10); for(auto i : vw) std::cout << i << ' '; puts(""); std::ranges::for_each( vw, [](int x){ std::cout << x << ' '; } ); puts("\n"); } * Örnek 3, #include #include #include #include #include "MyUtility.h" int main() { /* # OUTPUT # 954 605 118 462 156 834 78 905 397 594 214 638 176 715 133 265 515 587 79 278 ----------------------------------------------------------------------------- 954 605 118 462 156 834 78 905 397 594 156 462 118 605 954 156 462 118 954 */ std::vector ivec; MyUtility::Utility::rfill(ivec, 20, MyUtility::Random::Irand{0, 999}); MyUtility::Utility::print(ivec); // std::view::reverse(ivec) // Bu ifade bir "view", aynı zamanda da bir "range". for(auto i : std::views::take(ivec, 10)) // İş bu "range" içerisinde, "ivec" içerisindeki ilk on öğe vardır. std::cout << i << ' '; puts("\n"); // İş bu "range" içerisinde, yukarıdaki 10 öğenin baştan beş tanesinin ters sıralanmış hali vardır. for(auto i : std::views::reverse(std::views::take(ivec, 5))) std::cout << i << ' '; puts("\n"); // İş bu "range" içerisinde, yukarıdaki beş öğeden ikiye tam bölünenler vardır. for(auto i: std::views::filter(std::views::reverse(std::views::take(ivec, 5)), [](int x){ return x%2==0; })) std::cout << i << ' '; puts("\n"); } * Örnek 4, #include #include #include #include #include "MyUtility.h" int main() { /* # OUTPUT # 615 663 279 487 212 659 201 366 175 459 973 551 249 97 32 967 207 335 840 674 ----------------------------------------------------------------------------- 212 366 */ std::vector ivec; MyUtility::Utility::rfill(ivec, 20, MyUtility::Random::Irand{0, 999}); MyUtility::Utility::print(ivec); // Yukarıdaki "ivec" dizisinin ilk 10 öğesinden ikiye tam bölünenleri aldık: for( auto i : ivec | std::views::take(10) | std::views::filter( [](int x){ return x%2==0; } ) ) // Pipe-line mechanism. std::cout << i << ' '; puts("\n"); } Anımsanacağı üzere "view" lar "light-weight range" olarak da geçer ve bir "view" oluşturabilmek için birden fazla imkana sahibiz. Bunlardan ilki doğrudan o "view" türünden bir nesne oluşturmak ki bu durumda şablon argümanlarını bizim yazmamız gerekmektedir. Bu da hem okuma hem de yazma zahmeti doğurmaktadır. Diğer yöntemler ise "Range Adaptors" ve "Range Factories" nesnelerini kullanmaktır. >>> "Range Adaptors" ve "Range Factories": Öyle fonksiyon nesneleridir ki argüman olarak bir ya da birden fazla "range" alıp, belirli niteliğe sahip bir "range" döndürürler. Bu niteliklerden en önemlisi, geri döndürdükleri "range" in "view" konseptini desteklemesidir. Bir diğer deyişle; bunlar öyle adaptörlerdir ki argüman olarak "range" alır. Geri dönüş değeri olarak da "view" kavramına adapte edilmiş bir "range" döndürürler. Yani "view" niteliğinde bir "range" döndürürler. Pekiyi bu nesneleri kullanmanın avantajları nelerdir? Şöyleki; >>> Aşağıdaki örnekte de görüldüğü üzere "take" ile elde ettiğimiz nesne de bir "range" fakat "view" için adapte edilmiş, yani "view" özelliği olan, bir "range". * Örnek 1, #include #include #include int main() { std::vector vec{ 4, 6, 7, 9, 4, 2, 9, 1 }; auto vw = std::views::take(vec, 4); static_assert(std::ranges::view); return 0; } >>> Böylesi adaptörler ile elde edilen "range" ler daha az yer kapladıklarından, kopyalama maliyetleri de düşüktür. * Örnek 1, #include #include #include int main() { /* # OUTPUT # sizeof vec: 24 => 4 6 7 9 4 2 9 1 sizeof vec_range: 24 => 4 6 7 9 sizeof vw: 16 => 4 6 7 9 */ std::vector vec{ 4, 6, 7, 9, 4, 2, 9, 1 }; std::cout << "sizeof vec: " << sizeof(vec) << " => "; for(auto i : vec) std::cout << i << ' '; std::cout << '\n'; std::vector vec_range(vec.begin(), vec.begin() + 4); std::cout << "sizeof vec_range: " << sizeof(vec_range) << " => "; for(auto i : vec_range) std::cout << i << ' '; std::cout << '\n'; auto vw = std::views::take(vec, 4); std::cout << "sizeof vw: " << sizeof(vw) << " => "; for(auto i : vw) std::cout << i << ' '; std::cout << '\n'; return 0; } >>> "Lazy Evaluation" olmalarıdır. Yani biz o "range" den öğe talep ettikçe adapte işlemi yapılmaktadır. * Örnek 1, #include #include #include int main() { /* # OUTPUT # === === C++ C++ === */ std::vector vec{ 4, 6, 7, 9, 4, 2, 9, 1 }; std::cout << "===\n"; auto filter = std::views::filter(vec, [](int a){ std::cout << "C++\n"; return a%3==0; }); // Bu aşamada ekrana "C++" yazısının çıkmadığı görülmektedir. std::cout << "===\n"; auto filter_begin = filter.begin(); // Bu aşamada ekrana iki adet "C++" yazısının çıktığı görülmektedir. std::cout << "===\n"; return 0; } >>> Bir "Range Adaptor" ü ile elde ettiğim "range" i, yine başka bir "Range Adaptor" üne argüman olarak da geçebilirim. Yani "composability". Bu özellik "range" kütüphanesinin birincil avantajıdır. Çünkü böyle bir özellik olmasaydı, bir çok durumda kod yazma zahmeti oluşacak ve ilave kopyalamalar yapmak gerekecekti. * Örnek 1.0, STL 1.0 sürümü ile yapılış. #include #include #include #include int main() { /* # OUTPUT # 4 6 7 9 4 2 9 1 8 12 7 4 6 4 2 8 12 16 36 16 4 64 144 */ std::vector vec{ 4, 6, 7, 9, 4, 2, 9, 1, 8, 12, 7 }; for(auto i : vec) std::cout << i << ' '; std::cout << '\n'; // I. "vec" nesnesi içerisindeki çift olan öğeleri alacağız: std::vector temp; std::copy_if( vec.begin(), vec.end(), std::back_inserter(temp), [](int x){ return x%2==0; } ); for(auto i : temp) std::cout << i << ' '; std::cout << '\n'; // II. Almış olduğumuz çift sayıların karelerini bir "destination" // öğesine yazacağız. std::vector dest; std::transform( temp.begin(), temp.end(), std::back_inserter(dest), [](int x){ return x*x; } ); for(auto i : dest) std::cout << i << ' '; std::cout << '\n'; return 0; } * Örnek 1.1, STL 2.0 sürümü ile yapılış. (https://wandbox.org/permlink/jb2Uo9DJdT1U5m7q) #include #include #include #include #include namespace rng = std::ranges; namespace vw = std::views; void filter_and_transform(const std::vector& vec) { for(auto i : vec) std::cout << i << ' '; std::cout << '\n'; auto filter_and_transform = vw::transform( vw::filter( vec, [](int x){ return x%2==0; } ), [](int x){ return x*x; } ); std::cout << std::format("Filtered & Transformed Elements: {}\n", filter_and_transform); } void filter_and_transform_with_piping(const std::vector& vec) { for(auto i : vec) std::cout << i << ' '; std::cout << '\n'; auto filter_and_transform = vec | vw::filter([](int x){ return x%2==0; }) | vw::transform([](int x){ return x*x; }); std::cout << std::format("Filtered & Transformed Elements: {}\n", filter_and_transform); } int main() { /* # OUTPUT # 4 6 7 9 4 2 9 1 8 12 7 Filtered & Transformed Elements: [16, 36, 16, 4, 64, 144] 4 6 7 9 4 2 9 1 8 12 7 Filtered & Transformed Elements: [16, 36, 16, 4, 64, 144] */ std::vector vec{ 4, 6, 7, 9, 4, 2, 9, 1, 8, 12, 7 }; // I. "vec" nesnesi içerisindeki çift olan öğeleri alacağız. // II. Almış olduğumuz çift sayıların karelerini bir "destination" // öğesine yazacağız. filter_and_transform(vec); filter_and_transform_with_piping(vec); return 0; } Pekiyi "Range Adaptors" ile "Range Factories" arasındaki temel fark nedir? "Range Adaptors" nesneleri bir "range" i alıp bir "range" döndürmektedir. Fakat "Range Factories", bizden bir "range" almadan bir "range" döndürmektedir. Yani o "range" i kendisi oluşturmaktadır. * Örnek 1, #include #include int main() { auto vw = std::views::iota(10); auto rng = std::views::take(5); for(auto i : vw | rng) std::cout << i << ' '; // 10 11 12 13 14 std::cout << '\n'; return 0; } * Örnek 2, #include #include #include #include "MyUtility.h" using namespace MyUtility; int main() { /* # INPUT # iki sayi girin: 1 100 */ /* # OUTPUT # 97 89 83 79 73 71 67 61 59 53 47 43 41 37 31 29 23 19 17 13 11 7 5 3 2 */ namespace rng = std::ranges; namespace vw = std::views; int start_point, end_point; std::cout << "iki sayi girin: "; std::cin >> start_point >> end_point; auto theFilter = vw::iota(start_point, end_point) | vw::reverse | vw::filter(Utility::isprime); for (auto i : theFilter) std::cout << i << ' '; std::cout << '\n'; } Buradaki en önemli "Range Adaptors" / "Range Factories" nesneleri olarak "common" ve "subrange" nesnelerini örnek gösterebiliriz. Bu adaptörler ise şu işlevleri yerine getirmektedir: >>> "std::ranges::views::common" : Bir adet "range" i argüman olarak göndeririz. İş bu argümanın niteliğine göre bize üç farklı "range" geri döndürmektedir. Şöyleki; -> Eğer gönderdiğimiz "range", bir "Common Range" niteliğindeyse, aynısını geri döndürmektedir. -> Eğer "view" olmayan bir "range" ise ama "begin" ve "end" in geri dönüş değerleri aynıysa, "ref_view" geri döndürmektedir. "ref_view" konusu ileride ele alınacaktır. -> Yukarıdaki ikisi haricinde bir "range" ise, yani "begin" ve "end" in geri dönüş değerleri farklıysa, "common_view" geri döndürmektedir. Buradan hareketle geri dönüş değeri ya gönderdiğimiz "view" in kendisi, ya "common_view" ya da "ref_view" olmaktadır. * Örnek 1, #include #include #include int main() { std::list myList(10); // "list" türünde bir nesne oluşturulmuş. // "take" isimli adaptör kullanılarak bir nesne oluşturulmuş. auto my_take = std::views::take(myList, 4); // "iota" isimli bir "factory" kullanılarak bir nesne oluşturulmuş. auto my_iota = std::views::iota(35, 45); auto x = std::views::common(myList); // "x" is a "std::ranges::ref_view". Çünkü "begin" ve "end" ile elde edilenler // aynı tür fakat oluşturulan "range" bir "view" DEĞİL. auto y = std::views::common(my_take); // "y" is a "std::ranges::common_view". Çünkü "begin" ve "end" ile elde edilenler // aynı tür DEĞİL. auto z = std::views::common(my_iota); // "z" is a "std::ranges::iota_view". Çünkü argümanımız hem "view" hem de // "begin" ve "end" ile elde edilenler aynı tür. } * Örnek 2, #include #include #include #include #include "MyUtility.h" template struct Sentinel { bool operator==(auto pos) const { return *pos == ENDVAL; } }; int main() { /* # OUTPUT # 5 */ std::vector ivec(20); // 20 elemanlı bir dizimiz var ve her birinin değeri "0". ivec[5] = -1; // 5 indisli öğenin değerini "-1" yaptık. // "range" nin "begin" ile "end" türleri farklı türlerdir. // Yani "Common Range" niteliğinde DEĞİL. std::ranges::subrange sr(ivec.begin(), Sentinel<-1>()); // "Common Range" OLMADIĞI İÇİN SENTAKS HATASI. // auto e = std::count(sr.begin(), sr.end(), 0); // Artık "Common Range" niteliğinde. auto cv = std::views::common(sr); // Dolayısıyla "STL 1.0" içerisinde bulunan "count" fonksiyonuna // argüman olarak geçebiliriz. Yani sıfırıncı indisli öğe ile // beş indisli öğe arasındaki öğelerden değeri "0" olanları // sayacağız. auto n = std::count(cv.begin(), cv.end(), 0); std::cout << n << '\n'; } * Örnek 3, #include #include #include #include int main() { /* # OUTPUT # 61 */ std::vector ivec{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 }; // "ivec" içerisindeki baştan itibaren ilk // basamağı "7" olanlardan bir "range" oluşturacaktır. auto rng = ivec | std::views::filter([](int x){ return x%10==7; }); auto sum = std::accumulate(rng.begin(), rng.end(), 0); std::cout << sum << '\n'; } * Örnek 4, #include #include #include #include #include "MyUtility.h" using namespace MyUtility; int main() { std::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<20; }); // "ivec" içerisindeki baştan itibaren değeri // 20'den küçük olanlar ile bir "range" oluşturacaktır. // Fakat "take_while" ile oluşturulan "range", bir // "common_range" DEĞİL. // Bir "range" nin "common_range" olup olmadığını aşağıdaki şekilde sınayabiliriz. static_assert(std::ranges::common_range); // error: static assertion failed } * Örnek 5, #include #include #include #include int main() { /* # OUTPUT # sum = 17 */ std::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 rng = std::views::common( std::views::take_while( ivec, [](int x){ return x<10; } ) ); auto sum = std::accumulate(rng.begin(), rng.end(), 0); std::cout << "sum = " << sum << '\n'; static_assert(std::ranges::common_range); } >>> "std::ranges::subrange" : Bir "range" içerisindeki başka bir "range" demektir. Argüman olarak bir "range" gönderebildiğimiz gibi iki adet iteratör de gönderebiliriz. Dolayısıyla geri dönüş değerinin ne olduğu da gönderdiğimiz argümanlara göre değişkenlik gösterecektir. Üçüncü parametresine geçeceğimiz argümana göre, elde edilecek "range" nin "sized_range" olup olmadığını da belirleyebiliriz. * Örnek 1, #include #include #include #include #include int main() { /* # OUTPUT # sizeof sr1: 16 1 9 1 3 5 7 9 [1, 3, 5, 7, 9] [1, 3, 5, 7, 9] */ std::vector ivec{ 1, 3, 5, 7, 9 }; std::ranges::subrange sr1{ ivec }; // CTAD std::cout << "sizeof sr1: " << sizeof(sr1) << '\n'; static_assert(std::ranges::view); auto& r1 = sr1.front(); auto& r2 = sr1.back(); std::cout << r1 << ' ' << r2 << '\n'; std::ranges::subrange sr2{ ivec.begin(), ivec.end() }; for(const auto i : sr2) std::cout << i << ' '; std::cout << std::format("\n{}\n{}\n", sr1, sr2); // C++23 } * Örnek 2, #include #include #include #include template class Sentinel { public: bool operator==(auto x) const { return *x==ENDVAL; } }; int main() { /* # OUTPUT # [1, 3, 5, 7, 9] */ std::vector ivec{ 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 }; std::ranges::subrange sb(ivec.begin(), Sentinel<8>{}); std::cout << std::format("{}\n", sb); // C++23 } * Örnek 3, #include #include #include #include template struct Sentinel { bool operator==(const auto x) const { return *x==ENDVAL; } }; // Constraint Template Parameter: "range" konseptinin "satisfied" olması gerekmektedir. // Yani ilgili konsepti sağlayan hem "R-Value" hem "L-Value" ile çağrılabilir. void print(std::ranges::range auto&& r) { for(const auto& val: r) std::cout << val << ' '; std::cout << '\n'; } int main() { /* # OUTPUT # 0 1 2 3 4 0 1 2 3 4 */ // auto vw = std::ranges::iota_view{ 0, 10 }; // std::vector ivec(vw.begin(), vw.end() + 10); auto ivec = std::ranges::iota_view{ 0, 10 } | std::ranges::to(); // Since C++23 std::ranges::subrange s1{ ivec.begin(), ivec.begin() + 5}; print(s1); std::ranges::subrange s2{ ivec.begin(), Sentinel<5>{}}; print(s2); } * Örnek 4.0, #include #include #include #include int main() { /* # OUTPUT # b1: 1 b2: 0 */ std::vector ivec{ 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 }; std::ranges::subrange sbvec{ next(ivec.begin()), prev(ivec.end()) }; constexpr bool b1 = std::ranges::sized_range; std::cout << "b1: " << b1 << '\n'; std::list ilist{ 0, 2, 4, 6, 8, 9, 7, 5, 3, 1 }; std::ranges::subrange sblist{ next(ilist.begin()), prev(ilist.end()) }; constexpr bool b2 = std::ranges::sized_range; std::cout << "b2: " << b2 << '\n'; } * Örnek 4.1, #include #include #include #include int main() { /* # OUTPUT # b1: 1 b2: 1 */ std::vector ivec{ 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 }; std::ranges::subrange sbvec{ next(ivec.begin()), prev(ivec.end()) }; constexpr bool b1 = std::ranges::sized_range; std::cout << "b1: " << b1 << '\n'; std::list ilist{ 0, 2, 4, 6, 8, 9, 7, 5, 3, 1 }; std::ranges::subrange sblist{ ilist.begin(), ilist.end(), ilist.size() }; constexpr bool b2 = std::ranges::sized_range; std::cout << "b2: " << b2 << '\n'; } * Örnek 5, #include #include #include int main() { /* # OUTPUT # b1: 1 b2: 1 */ int ar1[10]{}; int ar2[20]{}; static_assert(std::same_as); // Would Hold False using sbr_type1 = decltype(std::ranges::subrange{ar1}); using sbr_type2 = decltype(std::ranges::subrange{ar2}); static_assert(std::same_as); // Holds True /* * Buradan da şu sonucu çıkartmaktayız; * türleri farklı olan iki diziden, "subrange" oluşturarak, * aynı türden iki "range" oluşturabiliriz. */ } * Örnek 6, C++20, C++23 ile bir "range" in baştan veya sondan yarısını çekebileceğimiz bir "Range Adaptosr/Factories" bulunmamaktadır. Bu iş için aşağıdaki kodları kullanabiliriz. #include #include #include #include template auto left_half(Range r) { return std::ranges::subrange( std::begin(r), std::begin(r) + std::ranges::size(r) / 2 ); } template auto right_half(Range r) { return std::ranges::subrange( std::begin(r) + std::ranges::size(r) / 2, std::end(r) ); } int main() { /* # OUTPUT # [1, 3, 5, 7, 9] + [8, 6, 4, 2, 0] = [1, 3, 5, 7, 9, 8, 6, 4, 2, 0] */ std::vector ivec{ 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 }; std::cout << std::format("{} + {} = {}\n", left_half(ivec), right_half(ivec), ivec); } Pekala bizler bir "range" içerisindeki öğeleri bir "container" içerisine yazabilir miyiz? C++23 ile bunun şöyle çok kısa bir yolu vardır: * Örnek 1, #include #include #include #include #include "MyUtility.h" using namespace MyUtility; int main() { /* # INPUT # iki sayi girin: 1 100 */ /* # OUTPUT # Size: 25 => 97 89 83 79 73 71 67 61 59 53 47 43 41 37 31 29 23 19 17 13 11 7 5 3 2 */ namespace rng = std::ranges; namespace vw = std::views; int start_point, end_point; std::cout << "iki sayi girin: "; std::cin >> start_point >> end_point; auto theFilteredContainer = vw::iota( start_point, end_point ) | vw::reverse | vw::filter(Utility::isprime) | rng::to(); std::cout << "Size: " << theFilteredContainer.size() << "=> "; for (auto i : theFilteredContainer) std::cout << i << ' '; std::cout << '\n'; } Bu kütüphanedeki parametresi "Universal Referance Range" olanlar algoritmalar, "L-Value" ve/veya "R-Value" değerleri için de çağrılabilmektedir. "R-Value" ile çağrı yaptığımız zaman, ilgili "R-Value" ifadenin ömrü biteceği için, bahsi geçen algoritmaların geri dönüş değeri, "Dangling Iterator" oluşmaması için, "std::ranges::dangling" türünden olacaktır. Bu tür aslında boş bir "struct" şeklindedir. İş bu tür belirlemesi pekala derleme zamanında belli olmaktadır. * Örnek 1, #include #include #include std::vector get_vec() { return { 1, 3, 5, 7, 9 }; } int main() { auto iter = std::ranges::find(get_vec(), 5); // std::ranges::dangling iter = std::ranges::find(get_vec(), 5); } Fakat "std::ranges::dangling" türünden iteratörümüzü "dereference" ettiğimiz noktada sentaks hatası oluşacaktır çünkü bu türün "dereference" operatörü "overload" EDİLMEMİŞTİR. * Örnek 1, #include #include #include std::vector get_vec() { return { 1, 3, 5, 7, 9 }; } int main() { auto iter = std::ranges::find(get_vec(), 5); // std::ranges::dangling iter = std::ranges::find(get_vec(), 5); std::cout << *iter << '\n'; // error: no match for ‘operator*’ (operand type is ‘std::ranges::dangling’) } Özetle bu tip algoritmalara gönderilen ifadelerin değer kategorisine göre geri dönüş değerinin türü, derleme aşamasında, belirlenmektedir. Şimdide aşağıdaki örneği inceleyelim: * Örnek 1, #include #include #include #include int main() { std::string name{ "Ulya" }; auto iter1 = std::ranges::find(std::string_view{ name }, 'U'); std::cout << *iter1 << '\n'; // OK auto iter2 = std::ranges::find(std::vector{ 1, 3, 5, 7, 9 }, 5); std::cout << *iter2 << '\n'; // error: no match for ‘operator*’ (operand type is ‘std::ranges::dangling’) } İşte burada devreye "borrowed_range" kavramı girmektedir. >> "borrowed_range" : Öyle bir "range" ki "a function can take it by value and return iterators obtained from it without danger of dangling". Yani öyle "range" ler ki fonksiyonlara argüman olarak geçilir ve fonksiyonlardan geri dönüş değeri olarak da iş bu "range" in iteratörü geri döndürüldüğünde "dangling iterator" oluşmaz. "std::ranges::borrowed_range" ismindeki "concept" ile bir "range" in "borrowed_range" olup olmadığını sınayabiliriz. Bir "range" in "borrowed_range" olması için ya "L-Value" bir "range" olması ya da "std::ranges::enable_borrowed_range" in ilgili sınıf türünden "specialization" ının "true" değerine çekilmiş olması gerekmektedir. Buraya kadarkileri özetlersek; iteratör döndüren fonksiyonların geri dönüş değeri, -> "R-Value" olan ve "borrowed_range" olmayan "range" ler söz konusu olduğunda "std::ranges::dangling" türü olacak. -> "R-Value" ve "borrowed_range" olan bir "range" gönderirsek, geri dönüş değeri "std::ranges::dangling" olmayacak. Yani doğrudan iteratör türü olacak. Ancak geri dönüş değerinin doğru olması için de ilgili "range" in hayatta olması gerekmektedir. -> "L-Value" olması durumunda, doğrudan iteratör türü olmaktadır. -> "std::ranges::enable_borrowed_range" in ilgili sınıf türünden "specialization" ının "true" olması halinde, doğrudan iteratör türü olmaktadır. Aşağıda bu hususa ilişkin bir örnek de verilmiştir: * Örnek 1, #include #include #include std::string foo() { return "Ulya"; }; int main() { auto iter3 = std::ranges::find(std::string_view{ foo() }, 'U'); std::cout << *iter3 << '\n'; // Burada bir sentaks hatasının olmaması, "Run Time" hatasının olmayacağı ANLAMINA GELMESİN. // "borrowed_range" olduğu için geri dönüş değerinin türü "std::ranges::dangling" DEĞİLDİR. // Fakat bu "range" in hayatta olup olmadığına ilişkin sorumluluk da bize aittir. } Pekiyi bizler hangi yol ve yöntemler ile bir "range" nesnesinden bir "view" nesnesi elde edebiliriz? Bu yöntemlerden ilki, dün de gördüğümüz "subrange" algoritmasını kullanmaktır. Diğer yöntemler ise şunlardır; "std::ranges::views::all" ve "std::ranges::counted" isimli algoritmaları kullanmaktır. >> "std::ranges::views::all": * Örnek 1, #include #include #include int main() { std::vector ivec{ 1, 4, 6, 7, 9, 12 }; auto v1 = std::views::all(ivec); // "v1" is of type "std::ranges::ref_view" // "v2" is of type "std::ranges::owning_view" auto v2 = std::views::all(std::vector{ 1, 4, 6, 7, 9, 12 }); auto v3 = std::views::all(v1); // "v3" is of type "std::ranges::ref_view" } >> "std::ranges::views::counted": * Örnek 1, #include #include #include #include int main() { std::vector ivec{ 1, 4, 6, 7, 9, 12 }; auto v1 = std::ranges::views::counted(ivec.begin(), 3); // "v1" is of type "std::span" std::list ilist{ 1, 4, 6, 7, 9, 12 }; auto v2 = std::ranges::views::counted(ilist.begin(), 3); // "v2" is of type "std::ranges::subrange" } Şimdi de evvelki derste değindiğimiz "Range Adaptors" / "Range Factories" lerin detaylarına değinelim; Bu algoritmaların iki kullanım yöntemi vardır; "Pipe Linening" sentaks kuralını işleyeceksek "|" atomunun sol tarafına kullanılacak "range", sağ tarafına ise ilgili algoritmayı geçiyoruz. Tabii eğer ilgili algoritmamız bir "predicate" vb. argümanlar alıyorsa, "()" içerisinde onları da belirtmemiz gerekmektedir. Eğer "Pipe Linening" sentaksını kullanmayacaksak, ilgili algoritmanın birinci argümanı kullanılacak "range", diğer argümanları ise varsa "predicate" vb. argümanlardır. >> "std::ranges::views::reverse" : İlgili "range" içerisindeki öğeleri ters-düz eder. * Örnek 1, #include #include #include int main() { std::vector ivec{ 3, 1, -1, 4, 7, 9, -1, 2, 6, 5, 8, 0, -4 }; // Usage as an adaptor (1): for (auto i: std::views::reverse(ivec)) std::cout << i << ' '; // -4 0 8 5 6 2 -1 9 7 4 -1 1 3 std::cout << '\n'; // Usage as an adaptor (2): auto rvs = std::views::reverse(ivec); for (auto i: rvs) std::cout << i << ' '; // -4 0 8 5 6 2 -1 9 7 4 -1 1 3 std::cout << '\n'; // Usage of Pipeline for (auto i: ivec | std::views::reverse) std::cout << i << ' '; // -4 0 8 5 6 2 -1 9 7 4 -1 1 3 std::cout << '\n' // Usage of explicit "reverse_view" object: std::ranges::reverse_view rv = std::views::reverse(ivec); for (auto i: rv) std::cout << i << ' '; // -4 0 8 5 6 2 -1 9 7 4 -1 1 3 std::cout << '\n'; // Work-Around: std::ranges::subrange sr{ ivec.rbegin(), ivec.rend() }; for (auto i: sr) std::cout << i << ' '; // -4 0 8 5 6 2 -1 9 7 4 -1 1 3 std::cout << '\n'; } >> "std::ranges::views::filter" : Argüman olarak bir adet de "predicate" alır ve "true" olan öğeler ile bir "range" oluşturur. * Örnek 1, #include #include #include int main() { std::vector ivec{ 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 }; auto const is_even = [](int x){ return !(x & 1); }; // Usage of the adaptor: auto v1 = std::ranges::views::filter(ivec, is_even); // [8, 6, 4, 2, 0] for (auto i : v1) std::cout << i << ' '; std::cout << '\n'; // Usage of Pipeline Rotation: auto v2 = ivec | std::ranges::views::filter(is_even); // [8, 6, 4, 2, 0] for (auto i : v2) std::cout << i << ' '; std::cout << '\n'; // Usage of explicit filter_view object: std::ranges::filter_view v3{ ivec, is_even }; // [8, 6, 4, 2, 0] for (auto i : v3) std::cout << i << ' '; std::cout << '\n'; } * Örnek 2, #include #include #include #include int main() { std::vector svec{ "ali", "mert", "can", "zeynep", "melike", "necati" }; char c = 'a'; for (const auto& s : std::ranges::views::filter(svec, [c](const auto& s){ return s.contains(c); })) std::cout << s << ' '; // ali can necati c = 'e'; for ( const auto& s : svec | std::ranges::views::reverse | std::ranges::views::filter([c](const auto& s){ return s.contains(c); }) ) std::cout << s << ' '; // necati melike zeynep mert } * Örnek 3, #include #include #include #include int main() { std::vector ivec{ 1, 2, 3, 4, 5, 15, 24, 33 }; const auto f = [](int x){ return x%5==0; }; std::ranges::filter_view my_filter_1{ ivec, f }; for(auto i: my_filter_1) std::cout << i << ' '; // 5 15 std::cout << '\n'; auto my_filter_2 = std::ranges::views::filter( ivec, f ); for(auto i: my_filter_2) std::cout << i << ' '; // 5 15 std::cout << '\n'; auto my_filter_3 = ivec | std::ranges::views::filter(f); for(auto i: my_filter_3) std::cout << i << ' '; // 5 15 std::cout << '\n'; } * Örnek 4.0, Aşağıdaki örnekte "vf" isimli "range" oluşturulurken bir "Sentinel" kullanılması durumunda, "vf.begin()" ve "vf.end()" fonksiyonları farklı türlerden olacağından, "dest" isimli vektörü hayata getiremezdik. #include #include #include #include int main() { std::vector source{ 1, 2, 3, 4, 5, 15, 24, 33 }; auto vf = source | std::ranges::views::filter([](int v){ return v%2==0; }); std::vector dest(vf.begin(), vf.end()); for (auto i : dest) std::cout << i << ' '; // 2 4 24 std::cout << '\n'; } * Örnek 4.1, #include #include #include #include int main() { std::vector source{ 1, 2, 3, 4, 5, 15, 24, 33 }; auto vf = source | std::ranges::views::filter([](int v){ return v%2==0; }) | std::ranges::to(); for (auto i : vf) std::cout << i << ' '; // 2 4 24 std::cout << '\n'; } >> "std::ranges::views::stride" : Argüman olarak "atlama kademesi" alır. Böylelikle üzerinde çalıştığı "range" deki öğelerden, atlama kademesi kadar atlayarak, yeni bir "range" oluşturur. C++23 ile dile eklenmiştir. * Örnek 1, #include #include #include int main() { std::vector ivec{ 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 }; // Usage of the adaptor: auto v1 = std::ranges::views::stride(ivec, 3); // [1, 7, 6, 0] for (auto i : v1) std::cout << i << ' '; std::cout << '\n'; // Usage of Pipeline Rotation: auto v2 = ivec | std::ranges::views::stride(3); // [1, 7, 6, 0] for (auto i : v2) std::cout << i << ' '; std::cout << '\n'; // Usage of explicit filter_view object: std::ranges::stride_view v3{ ivec, 3 }; // [1, 7, 6, 0] for (auto i : v3) std::cout << i << ' '; std::cout << '\n'; } >> "std::ranges::views::counted" : Bizden bir adet iteratör ve "how_many" argümanı alıyor. İteratörden başlayarak, "how_many" adedince ilerliyor. Bu iki nokta arasındaki öğelerden bir "range" oluşturur. Yalnız bunu "Pipeline" mekanizması ile kullanamayız. Ayrıca "std::ranges::counted_view" gibi bir şey de MEVCUT DEĞİLDİR. Dolayısıyla sadece ilgili adaptörü kullanabiliriz. * Örnek 1, #include #include #include int main() { std::vector ivec{ 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 }; // Usage of the adaptor: auto v1 = std::ranges::views::counted(ivec.begin(), 5); // [1, 3, 5, 7, 9] for (auto i : v1) std::cout << i << ' '; std::cout << '\n'; } >> "std::ranges::views::take" : Bir "range" içerisindeki ilk "n" tane öğeden "std::ranges::view" oluşturur. * Örnek 1, #include #include #include int main() { std::vector ivec{ 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 }; // Usage of the adaptor: auto v1 = std::ranges::views::take(ivec, 5); for (auto i : v1) std::cout << i << ' '; std::cout << '\n'; // 1 3 5 7 9 // Usage of Pipeline Rotation: auto v2 = ivec | std::ranges::views::take(5); for (auto i : v2) std::cout << i << ' '; std::cout << '\n'; // 1 3 5 7 9 // Usage of explicit filter_view object: std::ranges::take_view v3{ ivec, 5 }; for (auto i : v3) std::cout << i << ' '; std::cout << '\n'; // 1 3 5 7 9 } >> "std::ranges::views::drop" : Bir "range" içerisindeki ilk "n" tane öğeyi atlamak suretiyle, geri kalan öğelerden "std::ranges::view" oluşturur. * Örnek 1, #include #include #include int main() { std::vector ivec{ 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 }; // Usage of the adaptor: auto v1 = std::ranges::views::drop(ivec, 5); for (auto i : v1) std::cout << i << ' '; std::cout << '\n'; // 8 6 4 2 0 // Usage of Pipeline Rotation: auto v2 = ivec | std::ranges::views::drop(5); for (auto i : v2) std::cout << i << ' '; std::cout << '\n'; // 8 6 4 2 0 // Usage of explicit filter_view object: std::ranges::drop_view v3{ ivec, 5 }; for (auto i : v3) std::cout << i << ' '; std::cout << '\n'; // 8 6 4 2 0 } >> "std::ranges::views::take_while" : Argüman olarak bir "predicate" alır ve o "predicate" "true" değer döndürdüğü sürece, ilgili "range" in başındaki öğelerden, "std::ranges::view" oluşturur. * Örnek 1, #include #include #include int main() { std::vector ivec{ 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 }; auto is_even = [](int x){ return 0 < x; }; // Usage of the adaptor: auto v1 = std::ranges::views::take_while(ivec, is_even); for (auto i : v1) std::cout << i << ' '; std::cout << '\n'; // 1 3 5 7 9 8 6 4 2 // Usage of Pipeline Rotation: auto v2 = ivec | std::ranges::views::take_while(is_even); for (auto i : v2) std::cout << i << ' '; std::cout << '\n'; // 1 3 5 7 9 8 6 4 2 // Usage of explicit filter_view object: std::ranges::take_while_view v3{ ivec, is_even }; for (auto i : v3) std::cout << i << ' '; std::cout << '\n'; // 1 3 5 7 9 8 6 4 2 } >> "std::ranges::views::drop_while" : Argüman olarak bir "predicate" alır ve o "predicate" "true" değer döndürdüğü sürece, ilgili "range" in başındaki öğeleri atlamak suretiyle, "std::ranges::view" oluşturur. * Örnek 1, #include #include #include int main() { std::vector ivec{ 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 }; auto is_even = [](int x){ return 0 < x; }; // Usage of the adaptor: auto v1 = std::ranges::views::drop_while(ivec, is_even); for (auto i : v1) std::cout << i << ' '; std::cout << '\n'; // 0 // Usage of Pipeline Rotation: auto v2 = ivec | std::ranges::views::drop_while(is_even); for (auto i : v2) std::cout << i << ' '; std::cout << '\n'; // 0 // Usage of explicit filter_view object: std::ranges::drop_while_view v3{ ivec, is_even }; for (auto i : v3) std::cout << i << ' '; std::cout << '\n'; // 0 } * Örnek 2.0, #include #include #include int main() { std::vector pvec{ 2, 3, 5, 7, 9, 11, 13, 17, 19, 23, 29 }; auto filter = pvec | // Bu vektördeki... std::views::drop_while([](int x) { return x < 10; }) | //...10'dan küçük değerdeki öğeleri düşür => 11, 13, 17, 19, 23, 29 std::views::reverse | //...ve ters sırala => 29, 23, 19, 17, 13, 11 std::views::drop(3); //...ve baştan üç tanesi hariç, geri kalanlarını çek => 17, 13, 11 for (auto i: filter) std::cout << i << ' '; // 17 13 11 } * Örnek 2.1, #include #include #include #include int main() { std::vector pvec{ 2, 3, 5, 7, 9, 11, 13, 17, 19, 23, 29 }; auto my_list = pvec | // Bu vektördeki... std::views::drop_while([](int x) { return x < 10; }) | //...10'dan küçük değerdeki öğeleri düşür => 11, 13, 17, 19, 23, 29 std::views::reverse | //...ve ters sırala => 29, 23, 19, 17, 13, 11 std::views::drop(3) | //...ve baştan üç tanesi hariç, geri kalanlarını çek => 17, 13, 11 std::ranges::to(); // Elde ettiklerinden de "list" türünde bağlı liste oluştur. for (auto i: my_list) std::cout << i << ' '; // 17 13 11 } >> "std::ranges::views::slide" : Argüman olarak aldığı "range" i parçalara böler ve her bir parça, ikinci parametresindeki öğe kadar öğeye sahiptir. C++23 ile dile eklenmiştir. * Örnek 1, #include #include #include int main() { /* # OUTPUT # 1 3 3 5 5 7 7 9 9 8 8 6 6 4 4 2 2 0 1 3 5 3 5 7 5 7 9 7 9 8 9 8 6 8 6 4 6 4 2 4 2 0 1 3 5 7 3 5 7 9 5 7 9 8 7 9 8 6 9 8 6 4 8 6 4 2 6 4 2 0 */ std::vector ivec{ 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 }; // Usage of the adaptor: auto v1 = std::ranges::views::slide(ivec, 2); for (auto i : v1){ for (auto j: i) std::cout << j << ' '; std::cout << '\n'; } // Usage of Pipeline Rotation: auto v2 = ivec | std::ranges::views::slide(3); for (auto i : v2){ for (auto j: i) std::cout << j << ' '; std::cout << '\n'; } // Usage of explicit filter_view object: std::ranges::slide_view v3{ ivec, 4 }; for (auto i : v3){ for (auto j: i) std::cout << j << ' '; std::cout << '\n'; } } * Örnek 2, #include #include #include #include #include #include int main() { /* # OUTPUT # murat mert gul nihat cevahir jale seyhan -------------------------------------------- murat|mert|gul| mert|gul|nihat| gul|nihat|cevahir| nihat|cevahir|jale| cevahir|jale|seyhan| */ std::vector svec{ "murat", "mert", "gul", "nihat", "cevahir", "jale", "seyhan" }; std::ranges::copy(svec, std::ostream_iterator(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'; } std::cout << '\n'; } >> "std::ranges::views::adjacent" : Argüman olarak aldığı "range" i parçalara böler ve her bir parça, şablon parametresindeki öğe kadar öğeye sahiptir. C++23 ile dile eklenmiştir. "std::ranges::views::slide" bize "view" döndürürken, bu ise "tuple-like" obje döndürmektedir. * Örnek 1, #include #include #include int main() { std::vector ivec{ 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 }; // Usage of the adaptor: auto v1 = std::ranges::views::adjacent<2>(ivec); for (auto i : v1) std::cout << i << ' '; std::cout << '\n'; // [1,3] [5,7] [9,8] [6,4] [2,0] // Usage of Pipeline Rotation: auto v2 = ivec | std::ranges::views::adjacent<3>; for (auto i : v2) std::cout << i << ' '; std::cout << '\n'; // [1,3,5] [7,9,8] [6,4,2] [0] } >> "std::ranges::views::pairwise" : "std::ranges::views::slide" a parametre olarak "2" değerinin geçilmiş halidir. Geri döndürdüğü nesne bir "view" değil, "tuple-like" nesnedir. C++23 ile dile eklenmiştir. * Örnek 1, #include #include #include int main() { std::vector ivec{ 1, 3, 5, 7, 9, 8, 6, 4, 2, 0 }; // Usage of Pipeline Rotation: auto v2 = ivec | std::ranges::views::pairwise; for (auto i : v2) std::cout << i << ' '; std::cout << '\n'; // [1,3] [5,7] [9,8] [6,4] [2,0] } >> "std::ranges::views::transform" : Aldığı "range" içerisindeki öğeleri bir "callable" nesneye gönderir ve elde ettiği geri dönüş değerlerinden bir "view" oluşturur. * Örnek 1, #include #include #include int main() { std::string name{ "abcd" }; const auto to_upper = [](char c){ return static_cast(std::toupper(static_cast(c))); }; // Usage of the adaptor: auto v1 = std::ranges::views::transform(name, to_upper); for (auto i : v1) std::cout << i << ' '; std::cout << '\n'; // U L Y A Y U R U K // Usage of Pipeline Rotation: auto v2 = name | std::ranges::views::transform(to_upper); for (auto i : v2) std::cout << i << ' '; std::cout << '\n'; // U L Y A Y U R U K // Usage of explicit filter_view object: std::ranges::transform_view v3{ name, to_upper }; for (auto i : v3) std::cout << i << ' '; std::cout << '\n'; // U L Y A Y U R U K } >> "std::ranges::views::split" : Adeta "tokenization" işlevi görmektedir. Argüman olarak aldığı değeri bir nevi ayraç olarak kullanmak suretiyle, ilgili "range" i bölmektedir. * Örnek 1, #include #include #include int main() { std::vector ivec{ 1, 3, 5, 7, 3, 1, 9, 8, 6, 4, 3, 1, 2, 0 }; // Usage of the adaptor: auto v1 = std::ranges::views::split(ivec, 3); // [1] [5,7] [1,9,8,6,4] [1,2,0] // Usage of Pipeline Rotation: auto v2 = ivec | std::ranges::views::split(1); // [3,5,7,3] [9,8,6,4,3] [2,0] // Usage of explicit filter_view object: std::array arr{ 3,1 }; std::ranges::split_view v3{ ivec, arr }; // [1,3,5,7] [9,8,6,4] [2,0] } * Örnek 2, #include #include #include #include #include #include int main() { /* # OUTPUT # 2 5 4 2 9 8 7 5 6 */ std::vector ivec{ 2, 5, 1, 4, 1, 2, 9, 8, 7, 1, 5, 6 }; auto rng = ivec | std::views::split(1); for (auto i: rng){ for (auto j: i) std::cout << j << ' '; std::cout << '\n'; } } >> "std::ranges::views::chunk_by" : Bir adet "predicate" vardır ve ilgili "range" deki öğeler sıralı biçimde "true" olduğu müddetçe bu öğelerden bir "view" oluşturur. C++23 ile gelmiştir. * Örnek 1, #include #include #include int main() { std::vector ivec{ 1,2,0,2,4,5,8,4,6,3,5,2,4 }; // Usage of the adaptor: auto v1 = std::ranges::views::chunk_by(ivec, std::ranges::less{}); // [1,2] [0,2,4,5,8] [4,6] [3,5] [2,4] const auto diff_less3 = [](int x, int y){ return std::abs(x-y) < 3; }; auto v2 = std::ranges::views::chunk_by(ivec, diff_less3); // [1,2,0,2,4,5] [8] [4,6] [3,5] [2,4] // Usage of Pipeline Rotation: auto v3 = ivec | std::ranges::views::chunk_by(std::ranges::less{}); // [1,2] [0,2,4,5,8] [4,6] [3,5] [2,4] // Usage of explicit filter_view object: std::ranges::chunk_by_view v4{ ivec, std::ranges::less{} }; // [1,2] [0,2,4,5,8] [4,6] [3,5] [2,4] } >> "std::ranges::views::join" : İlgili "range" içerisindeki öğeleri tek bir "range" haline getirir. * Örnek 1, #include #include #include #include int main() { std::vector svec{ "Ulya", "Yuruk", "Istanbul" }; // Usage of the adaptor: auto v1 = std::ranges::views::join(svec); for (auto i : v1) std::cout << i << ' '; std::cout << '\n'; // U l y a Y u r u k I s t a n b u l // Usage of Pipeline Rotation: auto v2 = svec | std::ranges::views::join; // U l y a Y u r u k I s t a n b u l for (auto i : v2) std::cout << i << ' '; std::cout << '\n'; // Usage of explicit filter_view object: std::ranges::join_view v3{ svec }; // U l y a Y u r u k I s t a n b u l for (auto i : v3) std::cout << i << ' '; std::cout << '\n'; } * Örnek 2, #include #include #include #include int main() { std::vector svec{ "Ulya", "Yuruk", "Istanbul", "Uskudar" }; auto v1 = std::ranges::views::join(svec); for (auto i : v1) std::cout << i << ' '; std::cout << '\n'; // U l y a Y u r u k I s t a n b u l U s k u d a r auto v2 = svec | std::ranges::views::reverse | std::ranges::views::join; for (auto i : v2) std::cout << i << ' '; std::cout << '\n'; // U s k u d a r I s t a n b u l Y u r u k U l y a } >> "std::ranges::views::join_with" : Yine "std::ranges::views::join" gibi birleştirme işlemi yapar. Ek olarak araya istediğimiz "delimeter" karakterini de ekler. C++23 ile dile eklenmiştir. * Örnek 1, #include #include #include #include int main() { std::vector svec{ "Ulya", "Yuruk", "Istanbul", "Uskudar" }; auto v1 = std::ranges::views::join(svec); for (auto i : v1) std::cout << i << ' '; std::cout << '\n'; // U l y a Y u r u k I s t a n b u l U s k u d a r auto v2 = svec | std::ranges::views::reverse | std::ranges::views::join_with(','); for (auto i : v2) std::cout << i << ' '; std::cout << '\n'; // U s k u d a r , I s t a n b u l , Y u r u k , U l y a } >> "std::ranges::views::zip" : Argüman olarak iki ya da daha fazla "range" i argüman olarak alır. Geriye de "tuple-like" nesnelerden oluşan bir "view" DÖNDÜRÜR. Bir öğe bir "range", bir öğe diğer "range" den temin edilir. C++23 ile dile eklenmiştir. * Örnek 1, #include #include #include #include int main() { /* # OUTPUT # [1 A][2 h][3 m][4 e][5 t] */ std::string svec1{ "Ahmet" }; std::vector ivec{ 1, 2, 3, 4, 5 }; for (auto t : std::ranges::views::zip(ivec, svec1)) { auto [i, c] = t; std::cout << "[" << i << ' ' << c << "]"; } } * Örnek 2, #include #include #include #include #include int main() { std::vector ivec{ 2, 5, 8, 1, 3, 9 }; std::string name{ "Ulya Yuruk" }; std::vector dvec{ 2.2, 5.5, 8.8, 1.1, 3.3, 9.9 }; std::cout << std::format("{}\n", std::ranges::views::zip(ivec, name, dvec)); // [(2, 'U', 2.2), (5, 'l', 5.5), (8, 'y', 8.8), (1, 'a', 1.1), (3, ' ', 3.3), (9, 'Y', 9.9)] } >> "std::ranges::views::repeat" : C++23 ile dile eklenmiştir. Birinci parametresine öğeyi, ikinci parametresine kaç tane istediğimizi geçiyoruz. Eğer ikinci parametresine bir şey geçilmezse, sonsuz öğeden oluşan bir "view" elde edilecektir. Fakat bu şekilde yalın haliyle kullanmanın da bir manası kalmayacağından, "std::ranges::views::take" veya "std::ranges::views::drop" dan birisini kullanmalıyız. * Örnek 1, #include #include int main() { for (auto i: std::ranges::views::repeat(5, 7)) std::cout << i << ' '; // 5 5 5 5 5 5 5 std::cout << '\n'; for (auto i: std::ranges::views::repeat('A') | std::ranges::views::take(4)) std::cout << i << ' '; // A A A A std::cout << '\n'; } >> "std::ranges::views::iota" : Argüman olarak aldığı rakamdan başlamak suretiyle, bir artarak, "infinite range" oluşturmak için kullanılır. * Örnek 1, #include #include int main() { auto v = std::ranges::views::iota(10); for (auto i: v | std::ranges::views::take(15)) std::cout << i << ' '; // 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 } * Örnek 2, #include #include #include int main() { auto source = std::views::iota(10) //10'dan başlayarak birer artan... | std::views::take(20) //...ilk 20 rakamdan... | std::views::filter([](int i){ return i%2==0; }); //...bir "range" oluşturuldu. std::vector dest; std::ranges::copy(source, std::back_inserter(dest)); for (auto i : dest) std::cout << i << ' '; // 10 12 14 16 18 20 22 24 26 28 } >> "std::ranges::views::elements" : İlgili "tuple" nesnesinin şablon parametresi olarak geçilen indisindeki öğelerden bir "view" oluşturmaya yarar. * Örnek 1, #include #include #include #include #include #include int main() { // Vektörün her bir elemanı bir "tuple" nesnesi. std::vector>> my_vec{ { 12, "ulya", 123u }, { 24, "yuruk", 456u }, { 36, "uskudar", 789u } }; // "index" is a type of "std::tuple<>" for (const auto& index: my_vec) { // "i" is of type "int" // "s" is of type "std::string" // "b" is of type "std::bitset" auto[i, s, b] = index; std::cout << "[" << i << ", " << s << ", " << b << "]"; } std::cout << "\n--------------------------\n"; // "ivec" is a vector containing "int" types, obtained from the "tuple". auto ivec = my_vec | std::ranges::views::elements<0> | std::ranges::to(); for (auto i: ivec) std::cout << i << ' '; std::cout << '\n'; // "svec" is a vector containing "std::string" types, obtained from the "tuple". auto svec = my_vec | std::ranges::views::elements<1> | std::ranges::to(); for (auto i: svec) std::cout << i << ' '; std::cout << '\n'; // "bvec" is a vector containing "std::bitset" types, obtained from the "tuple". auto bvec = my_vec | std::ranges::views::elements<2> | std::ranges::to(); for (auto i: bvec) std::cout << i << ' '; std::cout << '\n'; } >> "std::ranges::views::keys" : "std::pair" çifti tutan "range" içerisindeki "key" değerlerinden bir "view" oluşturmaya yarar. * Örnek 1, #include #include #include #include #include #include int main() { std::vector> my_vector{ { 28, "ulya yuruk" }, { 34, "Istanbul" }, { 52, "Ordu" } }; for (const auto& s : std::views::keys(my_vector)) std::cout << s << ' '; // 28 34 52 std::cout << '\n'; } >> "std::ranges::views::values" : "std::pair" çifti tutan "range" içerisindeki "value" değerlerinden bir "view" oluşturmaya yarar. * Örnek 1, #include #include #include #include #include #include int main() { std::vector> my_vector{ { 28, "ulya yuruk" }, { 34, "Istanbul" }, { 52, "Ordu" } }; for (const auto& s : std::views::keys(my_vector)) std::cout << s << ' '; // 28 34 52 std::cout << '\n'; for (const auto& s : std::views::values(my_vector)) std::cout << s << ' '; // ulya yuruk Istanbul Ordu std::cout << '\n'; } * Örnek 2, #include #include #include #include #include #include int main() { std::vector> my_vector{ { 28, "ulya yuruk" }, { 34, "Istanbul" }, { 52, "Ordu" } }; for (const auto& s : std::views::keys(my_vector)) std::cout << s << ' '; // 28 34 52 std::cout << '\n'; for (const auto& s : std::views::values(my_vector)) std::cout << s << ' '; // ulya yuruk Istanbul Ordu std::cout << '\n'; for (const auto& [key, value] : std::views::zip(std::views::keys(my_vector), std::views::values(my_vector))) std::cout << key << " - " << value << ' '; // 28 - ulya yuruk 34 - Istanbul 52 - Ordu std::cout << '\n'; } Şimdi de genel örneklerle ilerlemeye devam edelim: * Örnek 1.0, "view" nesnelerinin arka planda "cache" mekanizmasının kullanmasının getirdikleri: #include #include #include int main() { /* # OUTPUT # # First Tour # The Value: 2 The Value: 7 The Value: 9 The Value: 5 The Value: 4 The Value: 10 The Value: 6 The Value: 7 # Second Tour # The Value: 4 The Value: 10 The Value: 6 The Value: 7 */ std::vector ivec{ 2, 7, 9, 5, 4, 10, 6, 7 }; auto v = ivec | std::ranges::views::filter( [](int x){ std::cout << "The Value: " << x << '\n'; return x % 5 == 0; } ); std::cout << "# First Tour #\n"; for (auto i: v) { //... } std::cout << '\n'; std::cout << "# Second Tour #\n"; for (auto i: v) { //... } std::cout << '\n'; /* * Görüleceği üzere "Second Tour" a üç indisli öğeden başladı. Bu da demektir ki bir şekilde * ilgili indisli öğe "cache" edilmiş. Böylelikle sıfırıncı, birinci ve ikinci indisli * öğeler BOŞ YERE TEKRARDAN GEZİLMEMİŞTİR. Bu durum performans için çok önemli bir şey * olsa bile arka plandaki mekanizmaya hakim olmadığımız zaman başımıza problem olabilir. * Peki hangi senaryolarda başımıza dert olabilir? * I. İlk defa dolaştıktan sonra iş bu "view" a ilişkin "range" modifiye edersek, ikinci * dolaşmamızda "cache" edilmiş lokasyondan başlayacağı için, "Tanımsız Davranış" gibi sorunlar * gibi problemler ile karşılaşabiliriz. Yani beklemediğimiz sonuçlar oluşabilir. */ } * Örnek 1.1, iş bu "cache" mekanizmasının doğurduğu lojik hataya ilişkin örnek. #include #include #include void print_range(std::ranges::input_range auto&& rng) { for (const auto& i: rng) std::cout << i << " "; std::cout << '\n'; } int main() { std::list ilist{ 2, 3, 5, 7, 11, 13 }; auto v = ilist | std::views::drop(3); // The range: 7, 11, 13 print_range(v); // OUTPUT: 7 11 13 ilist.push_front(-1); // The list: -1, 2, 3, 5, 7, 11, 13 print_range(v); // OUTPUT: 7 11 13 /* * İlk "print_range" çıktısı ile biz aslında "7, 11, 13" rakamlarını görmekteyiz ki aslında bu * beklenen bir şeydir. Fakat "ilist.push_front(-1);" çağrısı ile ilgili "range" üzerinde bir * modifikasyonda bulunduk. Fakat ilk "print_range" çağrısı ile "7" rakamının bulunduğu indis * "cache" edildiğinden, ikinci tura o indisten başladığını gördük. Her ne kadar burada bir * "Tanımsız Davranış" olmasa bile bir mantık hatası, yani lojik hata, vardır. */ } * Örnek 1.2, iş bu "cache" mekanizmasının doğurduğu lojik hataya ilişkin bir diğer örnek. #include #include #include void print_range(std::ranges::input_range auto&& rng) { for (const auto& i: rng) std::cout << i << " "; std::cout << '\n'; } int main() { /* # OUTPUT # */ std::vector ivec{ 2, 3, 5, 1, 2, 8, 7 }; // The Vector: 2, 3, 5, 1, 2, 8, 7 auto IsBigger = [](int i){ return i > 3; }; auto v = ivec | std::ranges::views::filter(IsBigger); // The Range: 5, 8, 7 print_range(v); // OUTPUT: 5 8 7 ++ivec[1]; ivec[2] = 0; // The Vector: 2, 4, 0, 1, 2, 8, 7 print_range(v); // OUTPUT: 0 8 7 /* * Yine çıktılardan da görüleceği üzere, ikinci turda elde ettiğimiz değerler üçten büyük * değerler değil. Bunun sebebi yine ilk turda "5" rakamının bulunduğu indisin "cache" * edilmesi ve ikinci tura bu indisten başlanmasıdır. */ } * Örnek 1.3, iş bu "cache" mekanizmasının doğurduğu sentaks hatasına ilişkin bir örnek. #include #include #include #include void print(const auto& rg) { for (const auto& elem: rg) std::cout << elem << ' '; std::cout << '\n'; } int main() { 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 | std::ranges::views::take(3)); // I: 1 2 3 print(ivec | std::ranges::views::drop(3)); // II: 4 5 6 7 8 9 print(ilist | std::ranges::views::take(3)); // III: 1 2 3 //print(ilist | std::ranges::views::drop(3)); // IV: Error // V: 4 5 6 7 8 9 for (const auto& elem: ilist | std::ranges::views::drop(3)) std::cout << elem << ' '; std::cout << '\n'; auto IsEven = [](const auto& val){ return val%2==0; }; //print(ivec | std::ranges::views::filter(IsEven)); // VI: Error /* * Errors: Buradaki sentaks hatalarının muhtemel nedeni, "print" fonksiyonundaki "rg" * parametresinin "const" olmasıdır. Dolayısıyla "print" fonksiyonu içerisinde ilgili * "range", "const" olarak ele alınmaktadır. Yani "const" bir nesne için arka plandaki * "cache" mekanizmasında kullanılan "non-const" bir fonksiyonun çağrılması olayıdır. */ } * Örnek 1.4.0, iş bu "cache" mekanizmasının doğurduğu tanımsız davranışa ilişkin bir örnek #include #include #include auto get_elems() { std::vector ivec{ 3, 6, 7, 9, 2, 1, 5, 8 }; //... return ivec | std::ranges::views::take(4); } int main() { auto v = get_elems(); for (auto i : v) std::cout << i << ' '; // 4 21957 32 0 std::cout << '\n'; /* * Çıktıdan da görüleceği üzere, "ivec" nesnesinin ömrü bitecektir. * İlgili "v" isimli "range" de aslında "for" döngüsündeki çağrı ile * oluşturulacağı için, ömrü biten bir nesne kullanılarak "range" * oluşturulmuş olacaktır. Otomatik ömürlü bir nesneyi adres veya * referans yolu ile döndürmekten bir farkı yoktur bu durumun. */ } * Örnek 1.4.1, Aşağıdaki örnekte taşıma semantiği devreye girdiğinden, tanımsız davranış meydana gelmeyecektir. #include #include #include auto get_elems() { return std::vector{3, 6, 7, 9, 2, 1, 5, 8} | std::ranges::views::take(4); } int main() { auto v = get_elems(); for (auto i : v) std::cout << i << ' '; // 3 6 7 9 std::cout << '\n'; } * Örnek 1.4.2, Aşağıdaki örnekte taşıma semantiği devreye girdiğinden, tanımsız davranış meydana gelmeyecektir. #include #include #include auto get_elems_I() { return std::vector{3, 6, 7, 9, 2, 1, 5, 8} | std::ranges::views::take(4); } auto get_elems_II() { std::vector ivec{3, 6, 7, 9, 2, 1, 5, 8}; return std::move(ivec) | std::ranges::views::take(4); } int main() { auto v = get_elems_I(); for (auto i : v) std::cout << i << ' '; // 3 6 7 9 std::cout << '\n'; auto vv = get_elems_II(); for (auto i : vv) std::cout << i << ' '; // 3 6 7 9 std::cout << '\n'; } Buradaki örnekleri özetlersek; "Care must be taken when modifying ranges used by views". > Hatırlatıcı Notlar: >> C++ dilinde "void" geri dönüş değerine sahip fonksiyonların kodlarında yalın "return;" ifadesini kullanarak da programın akışınının o fonksiyondan çıkmasını sağlatabiliriz. Benzer şekilde geri dönüş değeri yine "void" olan başka bir fonksiyonu da "return" deyimi içerisinde çağırarak da yine o fonksiyondan çıkabiliriz. Fakat bu durum C dilinde geçerli değildir. * Örnek 1, #include #include void bar() {} void foo() { //... if(true) return bar(); // C dilinde geçerli değil, fakat C++ // dilinde geçerlidir. else return; } int main() { foo(); } >> Şablon parametresi "T" için tür çıkarım mekanizması ile "auto" tür çıkarım mekanizması arasında neredeyse bir fark yoktur. Tek istisnası "std::initializer_list" için geçerlidir. "T" için tür çıkarımı yapılması SENTAKS HATASI olurken, "auto" için yapılan tür çıkarımı "std::initializer_list" yönüne olacaktır. * Örnek 1, #include template void foo(T x) {} // Buradaki tür çıkarımı "T" için, "x" için DEĞİL. template void bar(T& x) {} // Buradaki tür çıkarımı "T" için, "x" için DEĞİL. template void zar(T&& x) {} // Buradaki tür çıkarımı "T" için, "x" için DEĞİL. int main() { foo(10); // "T" is "int". So, "x" is also "int". auto val = 10; // int val = 10; int a[20]{}; // "T" is "int[20]". So, "x" is "int (&)[20]". No "array-decay". bar(a); auto& ref_val = a; // int (&ref_val)[20] = a; int x = 5; zar(x); // "T" is "int&". "x" is "int&" because of the reference-collapsing. zar(10); // "T" is "int". "x" is "int&&". No reference-collapsing. //////////////// foo({ 4, 7, 9, 1 }); // SENTAKS HATASI auto init_list = { 4, 7, 9, 1 }; // std::initializer_list init_list = { 4, 7, 9, 1 }; } * Örnek 2.0, Tür çıkarımının nasıl yapıldığını derleyiciye sormak için: #include template class TypeTeller; template void func(T&&) { TypeTeller x; } // Burada "TypeTeller" için bir tanım olmadığından, // "T" için hangi türün çıkarıldığını öğrenebiliriz. int main() { func(10); // "T" is "int". int x{}; func(x); // "T" is "int&". } * Örnek 2.1, #include template class TypeTeller; template void func(T&) { TypeTeller x; } int foo(void) { return 1; } int main() { func(foo); // "T" is "int(void)". No function-to-pointer conversation. // "x" is "int(&)(void)". } >> Standart kütüphanenin bize verdiği "type_identity" meta fonksiyonunun temsili implementasyonu: * Örnek 1, #include /////// BİZİMKİ template struct TypeIdentity { using type = T; }; template using TypeIdentity_t = TypeIdentity::type; template void foo(T, TypeIdentity_t) {} /////// STANDART KÜTÜPHANEDEKİ template void bar(T, std::type_identity_t) {} int main() { TypeIdentity_t x{}; // int x{}; foo(1.4, 5); // Normal şartlarda bu sentaks hatası oluşturur. bar(5, 1.4); // Normal şartlarda bu sentaks hatası oluşturur. } >> "namespace alias" : * Örnek 1, #include int main() { namespace chr = std::chrono; // namespace alias. } * Örnek 2, namespace Nec { namespace Erg { namespace CppCourse { int x; //... } } } int main() { namespace advanced_cpp_course = Nec::Erg::CppCourse; // namespace alias. advanced_cpp_course::x = 100; } * Örnek 3, #include int main() { namespace rng = std::ranges; namespace rng_v = std::ranges::views; } >> "Sentinel" kavramı için ön bilgilendirme: Aslında buradaki amaç o dizinin nerede bittiğinin belirtilmesidir. Dizinin boyutunun bir döngü ile öğrenilmesi yükümlülüğünü kaldırmaktadır. * Örnek 1, #include struct NullChar{ bool operator==(auto x)const { return *x == '\0'; } // Parametre "auto" olduğu için herhangi bir için // çağrılabilir. Yeterki "*" operatörünün operandı // olabilsin. }; template void Print(Iter beg, Sentinel end) { while(beg != end) // Derleyici "operator!=" çağrısını "operator==" çağrısına dönüştürmektedir. C++20 std::cout << *beg++ << ' '; } int main() { char name[100] = "Merve Nur Menekse"; Print(name, NullChar{}); // M e r v e N u r M e n e k s e /* * Eski STL olsaydı, "Print" fonksiyonuna "name" dizisinin * büyüklük bilgisini geçmek zorundaydık. Bu bilgiyi de yine * bir "for" döngüsü ile elde edecektik. Daha sonra "Print" * içerisinde ikinci bir "for" ile "name" dizisinin elemanlarını * ekrana yazdıracaktır. * Fakat artık ilgili dizinin büyüklük bilgisini öğrenmemize * gerek kalmadı. Sadece sonlandırıcı karakterin ne olduğunu * belirliyoruz. */ return 0; } * Örnek 2, #include #include #include #include template struct EnderSentinel { bool operator==(auto pos) const { return *pos == ENDVAL; } }; int main() { /* # OUTPUT # 5 3 */ std::vector ivec{ 1, 5, 7, 9, 2, 3, 6, 79, 90 }; auto iter = std::ranges::find(ivec.begin(), EnderSentinel<3>{}, 5); // "ivec" dizisi içerisinde "5" karakteri aranacak. // Fakat dizinin bittiği konum "3" değerinin bulunduğu // konum olacak. std::cout << *iter << '\n'; iter = std::ranges::find(ivec.begin(), EnderSentinel<3>{}, 90); // Şimdi de dizi içerisinde "90" karakteri aranacak fakat // dizinin bittiği konum "3" değerinin bulunduğu konum olacak. // "3" değerinin bulunduğu konum daha önce geleceğinden, "90" // karakterini bulamamış olacaktır. std::cout << *iter << '\n'; } * Örnek 3, #include #include #include #include template struct EnderSentinel { bool operator==(auto pos) const { return *pos == ENDVAL; } }; int main() { /* # OUTPUT # 1 5 7 9 2 3 6 79 90 1 2 5 7 9 3 6 79 90 */ std::vector ivec{ 1, 5, 7, 9, 2, 3, 6, 79, 90 }; for (auto i : ivec) std::cout << i << ' '; std::cout << '\n'; // "3" karakterinin bulunduğu konuma olanları sıralayacaktır. std::ranges::sort(ivec.begin(), EnderSentinel<3>{}); for (auto i : ivec) std::cout << i << ' '; std::cout << '\n'; } >>> "Unreachable Sentinel" kavramı hakkında ön bilgilendirme: * Örnek 1, Eskiden karşılaştırma yapılırken hem aranan değer ilgili dizinin öğeleri ile tek tek karşılaştırılıyor hem de ilgili dizinin o anki indeks değeri ile dizinin boyutu karşılaştırılıyordu. "std::unreachable_sentinel" kullanılması ile sadece aranan değer ile dizideki öğeler birbiri ile karşılaştırılmaktadır. FAKAT BU YÖNTEMİ SAĞLIKLI KULLANABİLMEK İÇİN ARANAN DEĞERİN İLGİLİ DİZİ İÇERİSİNDE BULUNDUĞUNU BİLİYOR OLMALIYIZ. AKSİ HALDE ÇALIŞMA ZAMANI HATASI ALIRIZ. #include #include #include #include #include #include #include "MyUtility.h" int main() { std::vector ivec(10); std::mt19937 eng{ std::random_device{}() }; std::uniform_int_distribution dist{ 0, 100 }; std::ranges::generate(ivec, [&]() { return dist(eng); }); std::ranges::copy(ivec, std::ostream_iterator{std::cout, " "}); // 3 75 4 100 62 23 78 1 1 20 puts("\n"); std::ranges::shuffle(ivec, eng); std::ranges::copy(ivec, std::ostream_iterator{std::cout, " "}); // 3 75 4 100 62 23 78 1 1 20 puts("\n"); std::ranges::iota(ivec, 0); MyUtility::Utility::print(ivec); int searched_value; std::cout << "Value to search: "; std::cin >> searched_value; auto iter{ std::ranges::find(ivec.begin(), std::unreachable_sentinel, searched_value) }; // Şimdi sadece "*iter" öğesinin "searched_value" değerine eşitliği sınanmaktadır. // Eğer "ivec.end()" kullanılsaydı, döngünün her turunda ayrıca "ivec.end()" karşılaştırması da // yapılacaktı. std::cout << "Found at " << iter - ivec.begin() << '\n'; } >> "std::invoke" : "functional" başlık dosyası içerisindedir. * Örnek 1, #include #include int func(int x, int y) { return x * y + 5; } int main() { auto val = std::invoke(func, 10, 20); std::cout << "val: " << val << '\n'; // val: 205 } * Örnek 2, #include #include class Myclass { public: int foo(int x) { return x * x + 5; } }; int main() { Myclass m; int ival{45}; auto val = std::invoke(&Myclass::foo, m, ival); std::cout << "val: " << val << '\n'; // val: 2030 } * Örnek 3, #include #include class Myclass { public: static int func() { return 100; } int foo(int x) { return x * x + 5; } }; int main() { // A member function pointer to static member function. int(*s_ptr)() = &Myclass::func; std::cout << "*s_ptr: " << s_ptr() << '\n'; // *s_ptr: 100 // A member function pointer to non-static member function. Myclass m; int(Myclass::*ptr)(int) = &Myclass::foo; // auto ptr = &Myclass::foo; std::cout << "*ptr: " << (m.*ptr)(s_ptr()) << '\n'; // *ptr: 10005 // A member function pointer to non-static member function using "auto" auto m_fp = &Myclass::foo; auto d_m = new Myclass; std::cout << "*d_m: " << ((*d_m).*m_fp)(s_ptr()) << '\n'; // *d_m: 10005 delete d_m; // Using "std::invoke" instead of directly using the member function pointers. auto m_fp2 = &Myclass::foo; auto result = std::invoke(m_fp2, m, s_ptr()); std::cout << "result: " << result << '\n'; // result: 10005 auto result2 = std::invoke(m_fp2, d_m, s_ptr()); std::cout << "result2: " << result2 << '\n'; // result2 : 10005 } * Örnek 4, #include #include struct Myclass { int x{10}; int y{100}; }; int main() { Myclass m; // A normal pointer: auto ptr_x = &m.x; // int* ptr_x = &m.x; // Data member pointer: auto ptr_y = &Myclass::y; // int Myclass::*ptr_y = &Myclass::y; // Using these pointers directly: std::cout << "Myclass::x : " << *ptr_x << '\n'; // Myclass::x : 10 std::cout << "Myclass::y : " << m.*ptr_y << '\n'; // Myclass::y : 100 // Using "std::invoke" via the data member pointer: std::cout << "Myclass::y : " << std::invoke(ptr_y, m) << '\n'; // Myclass::y : 100 } >> "std::accumulate" : * Örnek 1, #include #include #include #include #include "MyUtility.h" int main() { /* # OUTPUT # suheyla muslum efecan ciler esen sade muruvvet azmi fahri gurbuz ----------------------------------------------------------------------------- toplam = suheylamuslumefecancileresensademuruvvetazmifahrigurbuz */ std::vector svec; MyUtility::Utility::rfill(svec, 10, MyUtility::Utility::rname); MyUtility::Utility::print(svec); auto x = std::accumulate(svec.begin(), svec.end(), std::string{"toplam = "}); std::cout << x << '\n'; } * Örnek 2, #include #include #include #include #include "MyUtility.h" int main() { /* # OUTPUT # 270351 */ std::vector ivec{ 9, 17, 19, 93 }; auto x = std::accumulate(ivec.begin(), ivec.end(), 1, [](int a, int b){ return a*b; }); std::cout << x << '\n'; } * Örnek 3, #include #include #include #include #include "MyUtility.h" int main() { /* # OUTPUT # nuri mukerrem eda melek yavuz necmettin aslihan nazli baran nazli ----------------------------------------------------------------------------- 56 */ std::vector svec; MyUtility::Utility::rfill(svec, 10, MyUtility::Utility::rname); MyUtility::Utility::print(svec); const auto f = [](std::size_t len, const std::string& s){ return s.length() + len; }; auto x = std::accumulate(svec.begin(), svec.end(), 0u, f); std::cout << x << '\n'; } * Örnek 4, template< typename Iter, typename Init > Init Accumulate1(Iter beg, Iter end, Init init) { while(beg != end){ init = std::move(init) + *beg; ++beg; } return init; } template< typename Iter, typename SenType, typename Init, typename Op = std::plus<> > Init Accumulate2(Iter beg, SenType end, Init init, Op op = {}) { while(beg != end){ init = op(std::move(init), *beg); ++beg; } return init; } template< typename Iter, typename SenType, typename Init, typename Op = std::plus<> > requires std::input_iterator && std::sentinel_for Init Accumulate3(Iter beg, SenType end, Init init, Op op = {}) { while(beg != end){ init = op(std::move(init), *beg); ++beg; } return init; } template< std::input_iterator Iter, std::sentinel_for SenType, typename Init, typename Op = std::plus<> > Init Accumulate4(Iter beg, SenType end, Init init, Op op = {}) { while(beg != end){ init = op(std::move(init), *beg); ++beg; } return init; } template< std::input_iterator Iter, std::sentinel_for SenType, typename Init, typename Op = std::plus<> > Init Accumulate5(Iter beg, SenType end, Init init, Op op = {}) { while(beg != end){ init = std::invoke(op, std::move(init), *beg); ++beg; } return init; } template< std::input_iterator Iter, std::sentinel_for SenType, typename Init = std::iter_value_t, typename Op = std::plus<> > Init Accumulate6(Iter beg, SenType end, Init init = Init{}, Op op = {}) { while(beg != end){ init = std::invoke(op, std::move(init), *beg); ++beg; } return init; } template< std::input_iterator Iter, std::sentinel_for SenType, typename Init = std::iter_value_t, typename Op = std::plus<>, typename Proj = std::identity > Init Accumulate7(Iter beg, SenType end, Init init = Init{}, Op op = {}, Proj proj = {}) { while(beg != end){ init = std::invoke(op, std::move(init), std::invoke(proj, *beg)); ++beg; } return init; } template< std::ranges::input_rage R, class Init = std::ranges::range_value_t, typename Op = std::plus<>, typename Proj = std::identity > Init Accumulate7(R&& r, Init init = Init{}, Op op = {}, Proj proj = {}) { return Accumulate7( std::ranges::begin(r), std::ranges::end(r), std::move(init), std::move(op), std::move(proj) ); } >> "sized_range" : Bir "range" in "sized_range" olması için öyle bir ".size()" fonksiyonuna sahip olmalıdır ki "constant time" içerisinde bize "size" bilgisini döndürecek. Yani ".size()" fonksiyonu "constant time" içerisinde o "range" de bulunan öğe sayısını döndürmelidir. Bu da beraberinde şunu getirmektedir; bir "range" in "sized_range" olup olmaması, bir takım işlemlerin yapılabilecek ve yapılamayacak olduğunu belirtmektedir. * Örnek 1, #include #include #include int main() { namespace rng = std::ranges; static_assert(rng::sized_range>); // Holds True } * Örnek 2, #include #include #include #include int main() { namespace rng = std::ranges; static_assert(rng::sized_range>); // Holds Failed } >> Aşağıdaki örneği inceleyelim: * Örnek 1, #include #include #include #include // Şablon, "constraint" edilmiştir. Şablon parametresine geçilen argüman "input_range" tip // "constraint" sağlamıyorsa, sentaks hatası oluşacaktır. Öte yandan fonksiyonun parametresi // bir "Universal Reference" şeklindedir. Fonksiyonun ismi "get_min" şeklinde olup, geri // dönüş değeri "std::ranges::range_value_t" nin "Range" açılımı biçimindedir. Tabii geri // dönüş değeri yerine "auto" da yazabilirdik. template std::ranges::range_value_t get_min(Range&& rng) { // Buradaki "empty" ismindeki "Function Object", fonksiyona // geçtiğimiz "range" nin boş olup olmadığını sınamaktadır. if(std::ranges::empty(rng)) return std::ranges::range_value_t{}; // Burada ise "begin" ismindeki "Function Object" ile // fonksiyona geçilen "range" in ilk öğesini gösteren // iteratörü elde etmiş oluyoruz. Yani burada ilgili // "range" nin ilk öğesini en küçük kabul ettik. auto pos = std::ranges::begin(rng); auto min = *pos; // Bir sonraki konum ilgili "range" nin son konumu // olmadığı müddetçe "range" içerisinde ilerliyoruz. // Burada son konum, ilk konum ile aynı türden olmak // zorunda değildir. Yani "Common Range" olmayan bir // "range" i de fonksiyona geçebiliriz. Döngünün her // turunda da o konumdaki değeri kullanarak ilgili // "range" içerisindeki en küçük öğeyi bulmuş oluyoruz. while(++pos != std::ranges::end(rng)) if(*pos < min) min = *pos; return min; } int main() { /* # OUTPUT # 2 */ std::vector ivec{ 9, 7, 2, 6, 5, 4 }; std::cout << get_min(ivec); } >> "range" LER BİR TÜR BELİRTMEZLER, BİR "concept" TİRLER. Yani "range concept" ini "satisfy" eden varlıklara "range" denmektedir. >> Bir "view" oluşturabilmek için ilgili sınıfın "Default Ctor.", "Destructor", "Move Ctor." ve varsa "Copy Ctor." fonksiyonlarının "Constant Time" karmaşıklığında olması gerekmektedir. Fakat bu karmaşıklık garantisini derleyicinin koda bakarak anlaması mümkün olmadığından, sınıfı yazanın deklare etmesi gerekmektedir. template concept view = ranges::range && std::movable && ranges::enable_view; Görüldüğü üzere "std::ranges::enable_view" in "T" açılımının "true" olması gerekmektedir. * Örnek 1, #include #include int main() { static_assert(std::ranges::enable_view>); // error: static assertion failed std::vector ivec; auto vw = ivec | std::ranges::views::take(5); static_assert(std::ranges::enable_view); // OK } >> "std::ranges::views" kavramı için detaylı kaynak olarak "https://hackingcpp.com/" internet sitesini de kullanabiliriz.