C++23 での範囲の改善

C++23 のおかげで、コンテナーの作成がより便利になります。さらに、範囲ライブラリに新しいビューが追加されました。

範囲

C++23 は、C++11 や C++20 ほど重要な標準ではありません。これは、C++17 の伝統に則ったものです。これは主に COVID-19 の影響によるもので、年 4 回の対面会議がオンラインになったためです。基本的に、範囲ライブラリはこの規則の例外です。範囲には、いくつかの重要な追加が行われます。

C++23 に期待できることについて詳しく知っている場合は (それについて書く前に)、cppreference.com/compiler_support を調べてください。さらに良いことに、Steve Downey の優れた論文 (C++23 Status Report) を読んでください。

コンテナの構築

範囲からコンテナーを作成するのは複雑な作業でした。次の関数 range は、python2 の range 関数をシミュレートします。 Python2 の range 関数は熱心で、range ペンダントも同様です:さらに、Python の range 関数は list を返します 、しかし std::vector を採掘します .

// range.cpp

#include <iostream>
#include <range/v3/all.hpp>
#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; };
 for (int i: ranges::views::iota(begin) | ranges::views::stride(stepsize) 
 | ranges::views::take_while(boundary)) {
 result.push_back(i);
 }
 }
 else { // (6)
 begin++;
 end++;
 stepsize *= -1;
 auto boundary = [begin](int i){ return i < begin; };
 for (int i: ranges::views::iota(end) | ranges::views::take_while(boundary) 
 | ranges::views::reverse 
 | ranges::views::stride(stepsize)) {
 result.push_back(i);
 }
 }
 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 << "\n\n";
 
 // range(1, 50, 5) // (2)
 res = range(1, 50, 5);
 for (auto i: res) std::cout << i << " ";
 
 std::cout << "\n\n";
 
 // range(50, 10, -1) // (3)
 res = range(50, 10, -1);
 for (auto i: res) std::cout << i << " ";
 
 std::cout << "\n\n";
 
 // range(50, 10, -5) // (4)
 res = range(50, 10, -5);
 for (auto i: res) std::cout << i << " ";
 
 std::cout << "\n\n";
 
}

行 (1) から (4) の呼び出しは、出力を見ると非常に読みやすいはずです。

範囲呼び出しの最初の 2 つの引数は、作成された整数の先頭と末尾を表します。開始は含まれますが、終了は含まれません。 3 番目のパラメーターとしてのステップ サイズは、デフォルトでは 1 です。間隔 [begin, end] が減少する場合、ステップ サイズは負になる必要があります。そうでない場合は、空のリストまたは空の std::vector を取得します。

範囲の実装で少しごまかしています。関数 range::views::stride を使用しますが、これは C++20 の一部ではありません。 stride(n) は、指定された範囲の n 番目の要素を返します。 std::views::stride だと思います C++23 の一部になりますが、よくわかりません。したがって、この例では range v3 実装を使用しましたが、ranges ライブラリの C++20 実装は使用しませんでした。

行 (1) の range 関数の if 条件 (begin にプッシュします。

else の場合 (2 行目) では、ちょっとしたトリックを使用します。数値 [end++, begin++[] を作成し、境界条件が満たされるまで取得し、それらを反転させ (ranges::views::reverse)、各 n 番目の要素を取得します。

ここで、std::views::stride が C++23 の一部であると仮定しましょう。 std::ranges::to のおかげで、コンテナを作成するのは非常に簡単です。これは、以前の range の C++23 ベースの実装です。 関数。

std::vector<int> range(int begin, int end, int stepsize = 1) {
 std::vector<int> result{};
 if (begin < end) { 
 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 { 
 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;
} 

基本的に、 push_back を置き換えました std::vector での操作 新しい呼び出し std::ranges::to<std::vector>, で 2 行のコードを削除しました。これまでのところ、コンテナを作成するためのこの新しい便利な関数をサポートするコンパイラはありません。新しい range を作成しました 仕様の私の解釈に基づく機能。エラーが含まれている場合は、修正します。

C++20 の既存のアルゴリズム

C++23 の新しいビューを紹介する前に、C++20 の既存のビューを次に示します。

C++23 の新しいビュー

ここで、新しい見解を紹介したいと思います。可能であれば、短いコード例を提供します。

  • std::ranges::views::zip_transform, std::views::zip_transform

変換関数を適用して、タプルで構成されるビューを作成します。

cppreferene.com/zip_transform_view の優れた例を次に示します。

#include <list>
#include <array>
#include <ranges>
#include <vector>
#include <iostream>
 
void print(auto const rem, auto const& r) {
 for (std::cout << rem; auto const& e : r)
 std::cout << e << ' ';
 std::cout << '\n';
}
 
int main() {
 auto v1 = std::vector<float>{1, 2, 3};
 auto v2 = std::list<short>{1, 2, 3, 4};
 auto v3 = std::to_array({1, 2, 3, 4, 5});
 
 auto add = [](auto a, auto b, auto c) { return a + b + c; };
 
 auto sum = std::views::zip_transform(add, v1, v2, v3);
 
 print("v1: ", v1); // 1 2 3
 print("v2: ", v2); // 1 2 3 4
 print("v3: ", v3); // 1 2 3 4 5
 print("sum: ", sum); // 3 6 9
}

出力をソース コードに直接追加しました。

  • std::ranges::adjacent_view std::views::adjacent_view, std::ranges::adjacent_transform_view, そして std::views::adjacent_transform

隣接する要素への参照のタプルで構成されるビューを作成します。さらに、変換関数を適用できます。

これらの例は、提案 P2321R2 から直接引用したものです:

vector v = {1, 2, 3, 4};

for (auto i : v | views::adjacent<2>) {
 cout << '(' << i.first << ', ' << i.second << ") "; // prints: (1, 2) (2, 3) (3, 4)
}

for (auto i : v | views::adjacent_transform<2>(std::multiplies())) {
 cout << i << ' '; // prints: 2 6 12
}

  • std::ranges::join_with, そして std::views::join_with

入力範囲を平坦化してビューを作成します。要素間に区切り文字を置きます。

cppreference.com/join_with_view は、スペースが区切り要素である良い例を提供します。

#include <iostream>
#include <ranges>
#include <vector>
#include <string_view>
 
int main() {
 using namespace std::literals;
 
 std::vector v{"This"sv, "is"sv, "a"sv, "test."sv};
 auto joined = v | std::views::join_with(' ');
 
 for (auto c : joined) std::cout << c;
 std::cout << '\n';
}

  • std::views::chunk, そして std::views::chunk_by

範囲 R を重複しない N サイズのチャンクに分割してビューを作成します。さらに、述語を適用できます。

コード スニペットは、提案 P2442R1 および提案 P2443R1 からのものです。

std::vector v = {1, 2, 3, 4, 5};
fmt::print("{}\n", v | std::views::chunk(2)); // [[1, 2], [3, 4], [5]]
fmt::print("{}\n", v | std::views::slide(2)); // [[1, 2], [2, 3], [3, 4], [4, 5]]


std::vector v = {1, 2, 2, 3, 0, 4, 5, 2};
fmt::print("{}\n", v | std::views::chunk_by(ranges::less_equal{})); // [[1, 2, 2, 3], [0, 4, 5], [2]]

どちらのコード スニペットも、C++20 のフォーマット ライブラリにプロトタイプ ライブラリ fmt を使用します。 fmt には便利な関数 fmt::print があります std::print として C++23 の一部になる可能性があります .

  • std::views::slide

ビューと数値 N を取得して、N タプルのビューを作成します。

この例も提案 P2443R1 からのものです。

vector v = {1, 2, 3, 4};

for (auto i : v | views::slide(2)) {
 cout << '[' << i[0] << ', ' << i[1] << "] "; // prints: [1, 2] [2, 3] [3, 4]
}

次は?

先週、私は世論調査を行い、「次にどのメンタリング プログラムを実施すべきか?」と尋ねました。正直、この結果にはかなり驚きました。私は 2004 年から 2008 年までデザイン パターンを教えていましたが、あなたはすでにそれらを知っていて、C++20 または C++ を使用したクリーン コードが投票に勝つだろうと想定していました。その結果、今後の投稿の計画を変更しました。私の次の大きなトピックは、「C++ のデザイン パターンとアーキテクチャ パターン」です。この大きなトピックを終えたら、C++20 と C++23 に戻ります。