
C++23: Aralık ve std::generator iyileştirmeleri – ivobot
C++20 somut eşyordamlar sağlamaz ancak bunların uygulanması için bir çerçeve sağlar. Bu C++23 ile değişir. std::generator
ilk somut koroutindir.
Duyuru
Rainer Grimm uzun yıllardır yazılım mimarı, ekip ve eğitim yöneticisi olarak çalışmaktadır. C++, Python ve Haskell programlama dilleri üzerine makaleler yazmaktan hoşlanıyor, aynı zamanda özel konferanslarda sık sık konuşmaktan da hoşlanıyor. Modern C++ adlı blogunda C++ tutkusunu yoğun bir şekilde ele alıyor.
std::generator
C++23’teki Ranges kitaplık uzantısının bir parçasıdır. Bu nedenle bu yazıya C++20’deki Ranges kütüphanesi ve C++23’teki uzantısı ile başlamak istiyorum. Ama kısa keseceğim. C++20’deki Ranges kütüphanesi ve C++23’teki uzantısı hakkında zaten yazmıştım:
Tamamlamak istediğim tek bir hikaye var:
Pitonlar range
C++23’teki işlev
“Pythonik olarak aralık kitaplığıyla: aralık ve filtre” ve “Python’un aralık işlevi, ikincisi” adlı makalelerimde range-
Python 2’de Ranges kütüphanesi kullanılarak uygulanan işlev. Python 2 ve Python 3 arasındaki fark range-
İşlev, sürüm 2’nin istekli, ancak sürüm 3’ün tembel olmasıdır. Bu, sürüm 2’nin sayıları ürettiği, sürüm 3’ün ise isteğe bağlı olarak sayıları üreten bir oluşturucu döndürdüğü anlamına gelir. Sorunu yalnızca C++20’de Eric Niebler’in range-v3 kitaplığını kullanarak çözebildim. Özellikle bu özelliğe ihtiyacım vardı stride(N)
başka bir Görünüme kayar ve bunu her seferinde yapar N
öğe geri döner.
std::ranges::views::stride
C++23’ün bir parçasıdır ve GCC ve MSVC derleyicileri onu destekler. Ayrıca bu örnekte pratik fonksiyonunu kullanıyorum std::ranges::to.
Bu C++23 işlevi aralıklardan kapsayıcılar ve dizeler oluşturmak için kullanılabilir. Bu özellik şu anda yalnızca MSVC ve Clang derleyicileri tarafından desteklenmektedir.
Şimdi işte Python olanı range-
C++’daki işlev:
// rangeCpp23.cpp
#include <iostream>
#include <ranges>
#include <vector>
std::vector<int> range(int begin, int end, int stepsize = 1) {
std::vector<int> result{};
if (begin < end) { // (5)
auto boundary = [end](int i){ return i < end; };
result = std::ranges::views::iota(begin)
| std::views::stride(stepsize)
| std::views::take_while(boundary)
| std::ranges::to<std::vector>();
}
else { // (6)
begin++;
end++;
stepsize *= -1;
auto boundary = [begin](int i){ return i < begin; };
result = std::ranges::views::iota(end)
| std::views::take_while(boundary)
| std::views::reverse
| std::views::stride(stepsize)
| std::ranges::to<std::vector>();
}
return result;
}
int main() {
std::cout << std::endl;
// range(1, 50) // (1)
auto res = range(1, 50);
for (auto i: res) std::cout << i << " ";
std::cout << "nn";
// range(1, 50, 5) // (2)
res = range(1, 50, 5);
for (auto i: res) std::cout << i << " ";
std::cout << "nn";
// range(50, 10, -1) // (3)
res = range(50, 10, -1);
for (auto i: res) std::cout << i << " ";
std::cout << "nn";
// range(50, 10, -5) // (4)
res = range(50, 10, -5);
for (auto i: res) std::cout << i << " ";
std::cout << "nn";
}
(1) – (4) arasındaki çağrılar, MSVC derleyici çıktısına bakarak okunabilecek kadar kolay olmalıdır:
Aralık çağrısının ilk iki bağımsız değişkeni, oluşturulan sayıların başlangıcını ve bitişini temsil eder. Başlangıç dahildir, ancak son dahil değildir. Varsayılan olarak üçüncü parametre olarak adım boyutu 1’dir. [begin, end]
düşerse adım boyutu negatif olmalıdır. Aksi takdirde boş veya boş bir listeyle karşılaşırsınız std::vector<int>
.
Koşul eğer (begin < end
) ranges-
(1)’deki fonksiyonun okunması yeterince kolay olmalıdır: sağlanan tüm sayıları oluşturur begin
başlangıç (std::views::iota(begin)
), her n’inci elemanı al (std::views::stride(stepsize)
) ve sınır koşulu geçerli olduğu sürece bunu yapın (std::views::take_while(boundary)
). Sonunda onu ben yaratıyorum std::vector<int>
. İçinde else
-Durum (2) Küçük bir numara kullanıyorum: Sayıları yaratıyorum [end++, begin++[
, nehme sie, bis die Randbedingung erfüllt ist, drehe sie um (std::views::reverse
) und nehme jedes n-te Element.
Nun möchte ich mich den Koroutinen widmen.
std::generator
std::generator
in C++23 ist die erste konkrete Koroutine. Ein std::generator
erzeugt eine Folge von Elementen, in dem die Koroutine dort ihren Kontrollfluss wieder aufnimmt, wo sie pausiert hat.
// generator.cpp
#include <generator>
#include <ranges>
#include <iostream>
std::generator<int> fib() {
co_yield 0; // (1)
auto a = 0;
auto b = 1;
for(auto n : std::views::iota(0)) {
auto next = a + b;
a = b;
b = next;
co_yield next; // (2)
}
}
int main() {
for (auto f : fib() | std::views::take(10)) {
std::cout << f << " ";
}
}
Die Funktion fib
ist eine Koroutine. Diese Koroutine erzeugt einen unendlichen Strom von Fibonacci-Zahlen. Der Zahlenstrom beginnt mit 0 (1) und wird mit der nächsten Fibonacci-Zahl fortgesetzt (2). Die Range-based for-Schleife fordert explizit die ersten 10 Fibonacci-Zahlen an.
Bislang unterstützt kein Compiler std::generator
. Es lässt sich die Koroutinen-Natur von std::generator
schön an seinem Header studieren:
std::ranges::elements_of
kommt ins Spiel, wenn ein Generator rekursiv aufgerufen werden soll.
std::generator<int> fib() {
co_yield 0;
auto a = 0;
auto b = 1;
for(auto n : std::views::iota(0)) {
auto next = a + b;
a = b;
b = next;
co_yield next;
}
}
std::generator<int> outer() {
yield fib(); // (1)
yield std::ranges::elements_of(fib); // (2)
}
Der äußere Generator gibt in Zeile (1) den inneren std::generator<int>
zurück, aber in Zeile (2) die Werte des inneren Generators. Beide Koroutinen besitzen den gleichen Rückgabetyp.
std::bind_back
Entsprechend zu std::bind_front
in C++20 unterstützt C++23 std::bind_back
. Das folgende Programm bindFrontBack.cpp
zeigt die Anwendung der beiden Funktionen.
// bindFrontBack.cpp
#include <functional>
#include <iostream>
#include <string>
int main() {
std::cout << 'n';
auto add = [](std::string a, std::string b, std::string c) { return a + b + c; }; araba iki_üç = std::bind_front(ekle, "bir"); std::cout
Çıktı sayesinde program kendini açıklamalıdır.
Kısmi fonksiyon uygulamaları hakkında daha fazla bilgi edinin. std::bind, std::bind_front
VE std::bind_back
“Yazılım Geliştirme Teknikleri: Kısmi Fonksiyonların Uygulanması” adlı makalemde bulabilirsiniz.
Sıradaki ne?
Artık C++23 ile işim bitti. Bu yüzden altı yıl geriye gitmek istiyorum. Bir sonraki yazımda C++17’de neredeyse hiç bilinmeyen bir özellikten bahsedeceğim: polimorfik ayırıcılar.
(kendim)