改善された範囲付き反復子

従来の標準テンプレート ライブラリよりも範囲ライブラリを優先する理由は他にもあります。範囲反復子は、統一されたルックアップ ルールをサポートし、追加の安全性保証を提供します。

統一ルックアップ ルール

begin を呼び出す汎用関数を実装するとします。 特定のコンテナで。問題は、begin を呼び出す関数が コンテナでは、無料の begin を想定する必要があります 関数またはメンバー関数 begin ?

// begin.cpp

#include <cstddef>
#include <iostream>
#include <ranges>

struct ContainerFree { // (1)
 ContainerFree(std::size_t len): len_(len), data_(new int[len]){}
 size_t len_;
 int* data_;
};
int* begin(const ContainerFree& conFree) { // (2)
 return conFree.data_;
}

struct ContainerMember { // (3)
 ContainerMember(std::size_t len): len_(len), data_(new int[len]){}
 int* begin() const { // (4)
 return data_;
 }
 size_t len_;
 int* data_;
};

void callBeginFree(const auto& cont) { // (5)
 begin(cont);
}

void callBeginMember(const auto& cont) { // (6)
 cont.begin();
}
 
int main() {

 const ContainerFree contFree(2020);
 const ContainerMember contMemb(2023);

 callBeginFree(contFree); 
 callBeginMember(contMemb);

 callBeginFree(contMemb); // (7)
 callBeginMember(contFree); // (8)
 
}

ContainerFree (1 行目) フリー関数 begin があります (2 行目)、および ContainerMember (3行目) メンバー関数 begin を持っています (4 行目)。したがって、contFree 汎用関数 callBeginFree を使用できます 無料の関数呼び出し begin(cont) を使用する (5 行目)、および contMemb 汎用関数 callBeginMember を使用できます メンバー関数 call cont.begin を使用する (6 行目)。 callBeginFree を呼び出すと と callBeginMember 行 (7) および (8) の不適切なコンテナーがあると、コンパイルは失敗します。

2 つの異なる begin を提供することで、この問題を解決できます。 クラシックと範囲ベースの 2 つの方法で実装します。

// beginSolved.cpp

#include <cstddef>
#include <iostream>
#include <ranges>

struct ContainerFree {
 ContainerFree(std::size_t len): len_(len), data_(new int[len]){}
 size_t len_;
 int* data_;
};
int* begin(const ContainerFree& conFree) {
 return conFree.data_;
}

struct ContainerMember {
 ContainerMember(std::size_t len): len_(len), data_(new int[len]){}
 int* begin() const {
 return data_;
 }
 size_t len_;
 int* data_;
};

void callBeginClassical(const auto& cont) {
 using std::begin; // (1)
 begin(cont);
}

void callBeginRanges(const auto& cont) {
 std::ranges::begin(cont); // (2)
}
 
int main() {

 const ContainerFree contFree(2020);
 const ContainerMember contMemb(2023);

 callBeginClassical(contFree);
 callBeginRanges(contMemb);

 callBeginClassical(contMemb);
 callBeginRanges(contFree);
 
}

この問題を解決する古典的な方法は、 std::begin を持ってくることです いわゆるusing宣言でスコープに入れます(1行目)。範囲のおかげで、 std::ranges::begin を直接使用できます (2 行目). std::ranges::begin begin の両方の実装を考慮します :無料版とメンバー関数。

最後に、安全性について書きます。

安全

イテレータから始めましょう。

反復子

範囲ライブラリは、範囲にアクセスするために予想される操作を提供します。

基になる範囲にアクセスするためにこれらの操作を使用する場合、大きな違いがあります。 std::ranges で範囲アクセスを使用すると、コンパイルが失敗します 引数が右辺値の場合のバリアント。逆に、従来の std と同じ操作を使用すると、 名前空間は未定義の動作です。

// rangesAccess.cpp

#include <iterator>
#include <ranges>
#include <vector>

int main() {

 auto beginIt1 = std::begin(std::vector<int>{1, 2, 3});
 auto beginIt2 = std::ranges::begin(std::vector<int>{1, 2, 3});

}

std::ranges::begin 左辺値のオーバーロードのみを提供します。一時的なベクトル std::vector{1, 2, 3} 右辺値です。その結果、プログラムのコンパイルは失敗します。

省略形の lvalue と rvalue は、検索可能な値と読み取り可能な値を表します。

  • lvalue (ロケータブル値):ロケータブル値は、メモリ内に位置を持つオブジェクトであるため、そのアドレスを決定できます。左辺値には ID があります。
  • rvalue (読み取り可能な値):右辺値は、読み取り専用の値です。メモリ内のオブジェクトを表していないため、そのアドレスを特定できません。

左辺値と右辺値の短い説明が単純化されていることを認めなければなりません。値カテゴリの詳細を知りたい場合は、次の投稿値カテゴリをお読みください。

ちなみに、イテレータだけでなくビューもこれらの追加の安全性を保証します。

ビュー

ビューはデータを所有しません。したがって、ビューはデータの有効期間を延長しません。したがって、ビューは左辺値に対してのみ作成できます。一時的な範囲でビューを作成すると、コンパイルは失敗します。

// temporaryRange.cpp

#include <initializer_list>
#include <ranges>


int main() {

 const auto numbers = {1, 2, 3, 4, 5};

 auto firstThree = numbers | std::views::drop(3);  // (1)
 // auto firstThree = {1, 2, 3, 4, 5} | std::views::drop(3); // (2)

 std::ranges::drop_view firstFour{numbers, 4};  // (3)
 // std::ranges::drop_view firstFour{{1, 2, 3, 4, 5}, 4}; // (4)
 
}

行 1 と 3 が左辺値の数値で使用されている場合、すべて問題ありません。逆に、右辺値 std::initializer_list<int> {1, 2, 3, 4, 5} でコメントアウトされた 2 行目と 4 行目を使用すると、 、GCC コンパイラが詳細に文句を言います:

次は?

次回の投稿では、C++23 の未来を初めてのぞき見します。特に、範囲ライブラリには多くの改善が加えられます。 std::ranges::to あり 範囲からコンテナを構築する便利な方法。さらに、ほぼ 20 の新しいアルゴリズムを取得します。それらのいくつかを次に示します: std::views::chunk_by, std::views::slide, std::views::join_with, std::views::zip_transform,std::views::adjacent_transform .