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:

“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 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.

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.

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)

Haberin Sonu

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir