C++20 を使用したコンテナの便利な機能の追加

コンテナーから要素を削除したり、連想コンテナーに特定のキーがあるかどうかを尋ねたりするのは、複雑すぎます。 C++20 では話が変わったからです。

簡単に始めましょう。コンテナから要素を消去したい。

消去削除イディオム

わかった。コンテナから要素を削除するのは非常に簡単です。 std::vectoの場合 関数  std::remove.  を使用できます

// removeElements.cpp

#include <algorithm>
#include <iostream>
#include <vector>

int main() {

 std::cout << std::endl;

 std::vector myVec{-2, 3, -5, 10, 3, 0, -5 };

 for (auto ele: myVec) std::cout << ele << " ";
 std::cout << "\n\n";

 std::remove_if(myVec.begin(), myVec.end(), [](int ele){ return ele < 0; }); // (1)
 for (auto ele: myVec) std::cout << ele << " ";

 std::cout << "\n\n";

}

プログラム removeElemtens.cpp std::vector のすべての要素を削除します それはゼロより小さいです。簡単ですか?ここで、プロの C++ プログラマーなら誰でも知っている罠に陥ります。

std::remove または std::remove_if inline (1) は何も削除しません。 std::vector まだ同じ数の引数があります。どちらのアルゴリズムも、変更されたコンテナーの新しい論理エンドを返します。

コンテナーを変更するには、新しい論理エンドをコンテナーに適用する必要があります。

// eraseRemoveElements.cpp

#include <algorithm>
#include <iostream>
#include <vector>

int main() {

 std::cout << std::endl;

 std::vector myVec{-2, 3, -5, 10, 3, 0, -5 };

 for (auto ele: myVec) std::cout << ele << " ";
 std::cout << "\n\n";

 auto newEnd = std::remove_if(myVec.begin(), myVec.end(), // (1)
[](int ele){ return ele < 0; }); myVec.erase(newEnd, myVec.end()); // (2) // myVec.erase(std::remove_if(myVec.begin(), myVec.end(), // (3)
[](int ele){ return ele < 0; }), myVec.end()); for (auto ele: myVec) std::cout << ele << " "; std::cout << "\n\n"; }

行 (1) は、新しい論理終了 newEnd を返します。 コンテナ myVec の . myVec からすべての要素を削除するために、この新しい論理終了が行 (2) に適用されます。 newEnd から .行 (3) のように関数 remove と erase を 1 つの式に適用すると、この構造が「erase-remove-idiom」と呼ばれる理由が正確にわかります。

新しい関数 erase のおかげで および erase_if C++20 では、コンテナーから要素を消去する方がはるかに便利です。

erase そして erase_if C++20

erase で と erase_if 、コンテナを直接操作できます。対照的に、前に提示された消去削除イディオムは非常に冗長です ( eraseRemoveElements.cpp の 3 行目)。 ):erase アルゴリズム std::remove_if によって提供された 2 つの反復子が必要です .

新しい関数 erase を見てみましょう と erase_if 実際には意味します。次のプログラムは、いくつかのコンテナーの要素を消去します。

// eraseCpp20.cpp

#include <iostream>
#include <numeric>
#include <deque>
#include <list>
#include <string>
#include <vector>

template <typename Cont> // (7)
void eraseVal(Cont& cont, int val) {
 std::erase(cont, val);
}

template <typename Cont, typename Pred> // (8)
void erasePredicate(Cont& cont, Pred pred) {
 std::erase_if(cont, pred);
}

template <typename Cont>
void printContainer(Cont& cont) {
 for (auto c: cont) std::cout << c << " ";
 std::cout << std::endl;
}

template <typename Cont> // (6)
void doAll(Cont& cont) {
 printContainer(cont);
 eraseVal(cont, 5);
 printContainer(cont);
 erasePredicate(cont, [](auto i) { return i >= 3; } );
 printContainer(cont);
}

int main() {

 std::cout << std::endl;
 
 std::string str{"A Sentence with an E."};
 std::cout << "str: " << str << std::endl;
 std::erase(str, 'e'); // (1)
 std::cout << "str: " << str << std::endl;
 std::erase_if( str, [](char c){ return std::isupper(c); }); // (2)
 std::cout << "str: " << str << std::endl;
 
 std::cout << "\nstd::vector " << std::endl;
 std::vector vec{1, 2, 3, 4, 5, 6, 7, 8, 9}; // (3)
 doAll(vec);
 
 std::cout << "\nstd::deque " << std::endl;
 std::deque deq{1, 2, 3, 4, 5, 6, 7, 8, 9}; // (4)
 doAll(deq);
 
 std::cout << "\nstd::list" << std::endl;
 std::list lst{1, 2, 3, 4, 5, 6, 7, 8, 9}; // (5)
 doAll(lst);
 
}

行 (1) はすべての文字 e を消去します 指定された文字列から str. 行 (2) はラムダ式を同じ文字列に適用し、すべての大文字を消去します。

残りのプログラムでは、シーケンス コンテナーの要素 std::vecto r (3 行目)、 std::deque (4 行目)、 std::list (5行目)が消去されます。各コンテナで、関数テンプレート doAll (6 行目) が適用されます。 doAll 要素 5 と 3 より大きいすべての要素を消去します。関数テンプレート erase (7 行目) 新しい関数 erase を使用します 関数テンプレート erasePredicate (8 行目) 新しい関数 erase_if を使用します .

Microsoft Compiler のおかげで、これがプログラムの出力です。

新しい関数 eraseerase_if 標準テンプレート ライブラリのすべてのコンテナに適用できます。これは、次の便利な関数 contains には当てはまりません .

連想コンテナー内の要素の存在を確認する

関数 contains のおかげで 、連想コンテナに要素が存在するかどうかを簡単に確認できます。

ストップ、あなたは言うかもしれませんが、これは既に find または count で実行できます。

いいえ、どちらの機能も初心者向けではなく、それぞれに欠点があります。

// checkExistens.cpp

#include <set>
#include <iostream>

int main() {

 std::cout << std::endl;

 std::set mySet{3, 2, 1};
 if (mySet.find(2) != mySet.end()) { // (1)
 std::cout << "2 inside" << std::endl;
 }

 std::multiset myMultiSet{3, 2, 1, 2};
 if (myMultiSet.count(2)) { // (2)
 std::cout << "2 inside" << std::endl;
 } 

 std::cout << std::endl;

}

関数は期待される結果を生成します。

両方の呼び出しの問題を次に示します。 find call inline (1) は冗長すぎます。 count についても同じ議論が成り立ちます ラインで呼び出します (2)。 count call にもパフォーマンスの問題があります。要素がコンテナー内にあるかどうかを知りたい場合は、要素が見つかった時点で停止し、最後までカウントしないようにする必要があります。具体的なケースでは myMultiSet.count(2) 2 を返しました。

逆に、C++20 の contains メンバー関数は非常に便利に使用できます。

// containsElement.cpp

#include <iostream>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>

template <typename AssozCont>
bool containsElement5(const AssozCont& assozCont) { // (1)
 return assozCont.contains(5);
}

int main() {
 
 std::cout << std::boolalpha;
 
 std::cout << std::endl;
 
 std::set<int> mySet{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 std::cout << "containsElement5(mySet): " << containsElement5(mySet);
 
 std::cout << std::endl;
 
 std::unordered_set<int> myUnordSet{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 std::cout << "containsElement5(myUnordSet): " << containsElement5(myUnordSet);
 
 std::cout << std::endl;
 
 std::map<int, std::string> myMap{ {1, "red"}, {2, "blue"}, {3, "green"} };
 std::cout << "containsElement5(myMap): " << containsElement5(myMap);
 
 std::cout << std::endl;
 
 std::unordered_map<int, std::string> myUnordMap{ {1, "red"}, {2, "blue"}, {3, "green"} };
 std::cout << "containsElement5(myUnordMap): " << containsElement5(myUnordMap);
 
 std::cout << std::endl;
 
}

この例に追加することはあまりありません。関数テンプレート containsElement5 true を返します 連想コンテナーにキー 5 が含まれている場合。私の例では、連想コンテナーのみを使用しました std::setstd::unordered_setstd::map 、および std::unordered_set キーを複数回持つことはできません。

次は?

便利な機能については、次の投稿に続きます。 C++20 では、2 つの値の中点を計算できます。std::string 部分文字列で開始または終了し、 std::bind_front で callable を作成します .