C++11 の algorithm ライブラリと C++20 の ranges ライブラリでは、様々なデータコレクションに対して共通の操作ができます。これらの特徴についてまとめます。
サポートするコレクション
サポートするコレクション
STL コンテナ(vector, list, string, …)のほかに、組み込み配列も渡せます。
コレクションは少なくともシーケンシャルアクセス可能でなければなりません。一部の関数はランダムアクセス必須か、計算量が大きくなります。
#include <algorithm>
#include <iostream>
#include <vector>
#include <list>
int main(int argc, char* argv[]) {
// 配列
int array[] = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
auto found_array1 = std::find(array, array + 9, 1);
auto found_array2 = std::find(std::begin(array), std::end(array), 1);
auto found_array3 = std::ranges::find(array, 1);
std::sort(array, array + 9);
std::sort(std::begin(array), std::end(array));
std::ranges::sort(array);
// ランダムアクセス可能なコンテナ
std::vector<int> vector = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
auto found_vector1 = std::find(vector.begin(), vector.end(), 1);
auto found_vector2 = std::find(std::begin(vector), std::end(vector), 1);
auto found_vector3 = std::ranges::find(vector, 1);
std::sort(vector.begin(), vector.end());
std::sort(std::begin(vector), std::end(vector));
std::ranges::sort(vector);
// シーケンシャルアクセス可能なコンテナ
std::list<int> list = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
auto found_list1 = std::find(list.begin(), list.end(), 1);
auto found_list2 = std::find(std::begin(list), std::end(list), 1);
auto found_list3 = std::ranges::find(list, 1);
//動作するが、計算量が大きくなる
std::list<int> list2 = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
bool binary_search_list1 = std::binary_search(list2.begin(), list2.end(), 1);
bool binary_search_list2 = std::ranges::binary_search(list2.begin(), list2.end(), 1);
bool binary_search_list3 = std::ranges::binary_search(list2, 1);
//コンパイルエラー
//std::sort(list.begin(), list.end());
//std::sort(std::begin(list), std::end(list));
//std::ranges::sort(list);
// 文字列
std::string string = "674589123";
auto found_string1 = std::find(string.begin(), string.end(), '1');
auto found_string2 = std::find(std::begin(string), std::end(string), '1');
auto found_string3 = std::ranges::find(string, '1');
std::sort(string.begin(), string.end());
std::sort(std::begin(string), std::end(string));
std::ranges::sort(string);
return 0;
}
STLコンテナの対応状況
#include <algorithm>
#include <iostream>
#include <array>
#include <deque>
#include <vector>
#include <span>
#include <forward_list>
#include <list>
#include <map>
#include <unordered_map>
#include <set>
#include <unordered_set>
#include <queue>
#include <stack>
int main(int argc, char* argv[]) {
// ランダムアクセス可能なコンテナ
std::array<int, 9> array = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
std::ranges::sort(array);
std::deque<int> deque = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
std::ranges::sort(deque);
std::vector<int> vector = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
std::ranges::sort(vector);
std::span<int> span(vector);
std::ranges::sort(vector);
// シーケンシャルアクセス可能なコンテナ
std::forward_list<int> forward_list = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
auto found_forward_list = std::ranges::find(forward_list, 1);
std::list<int> list = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
auto found_list = std::ranges::find(list, 1);
std::map<int, std::string> map = { {6, "f"}, {7, "g"}, {4, "d"}, {5, "e"}, {8, "h"}, {9, "i"}, {1, "a"}, {2, "b"}, {3, "c"} };
auto found_map = std::ranges::find(map, std::pair<int const, std::string>{ 1, "a" });
std::multimap<int, std::string> multimap = { {6, "f"}, {7, "g"}, {4, "d"}, {5, "e"}, {8, "h"}, {9, "i"}, {1, "a"}, {2, "b"}, {3, "c"} };
auto found_multimap = std::ranges::find(multimap, std::pair<int const, std::string>{ 1, "a" });
std::unordered_map<int, std::string> unordered_map = { {6, "f"}, {7, "g"}, {4, "d"}, {5, "e"}, {8, "h"}, {9, "i"}, {1, "a"}, {2, "b"}, {3, "c"} };
auto found_unordered_map = std::ranges::find(unordered_map, std::pair<int const, std::string>{ 1, "a" });
std::unordered_multimap<int, std::string> unordered_multimap = { {6, "f"}, {7, "g"}, {4, "d"}, {5, "e"}, {8, "h"}, {9, "i"}, {1, "a"}, {2, "b"}, {3, "c"} };
auto found_unordered_multimap = std::ranges::find(unordered_multimap, std::pair<int const, std::string>{ 1, "a" });
std::set<int> set = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
auto found_set = std::ranges::find(set, 1);
std::multiset<int> multiset = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
auto found_multiset = std::ranges::find(multiset, 1);
std::unordered_set<int> unordered_set = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
auto found_unordered_set = std::ranges::find(unordered_set, 1);
std::unordered_multiset<int> unordered_multiset = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
auto found_unordered_multiset = std::ranges::find(unordered_multiset, 1);
// 終端しかアクセスできない
std::queue<int> queue({ 6, 7, 4, 5, 8, 9, 1, 2, 3 });
//auto found_queue = std::ranges::find(queue, 1);
std::priority_queue<int> priority_queue { {}, { 6, 7, 4, 5, 8, 9, 1, 2, 3 } };
//auto found_priority_queue = std::ranges::find(priority_queue, 1);
std::stack<int> stack({ 6, 7, 4, 5, 8, 9, 1, 2, 3 });
//auto found_stack = std::ranges::find(stack, 1);
return 0;
}
コレクションの指定方法
std::xxx(C++11): 対象の範囲 [first, last) のイテレータ―を指定します。
std::ranges::xxx(C++20): 対象の範囲 [first, last) のイテレータ―を指定します。
std::ranges::xxx(C++20): 対象の範囲を直接指定します。
std::begin(), std::end() は配列、コンテナ区別なく使用できます(cbegin, rbegin, crbegin なども同様)。
コレクション全体だけでなく、イテレーター(ポインター含む)・view を使うことで特定の範囲を渡すことができます。
#include <algorithm>
#include <iostream>
#include <vector>
int main(int argc, char* argv[]) {
// 配列
int a[] = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
// std::xxx(C++11): 対象の範囲 [first, last) のイテレータ―を指定します。
std::sort(a, a + 9);
std::sort(std::begin(a), std::end(a));
// std::ranges::xxx(C++20): 対象の範囲 [first, last) のイテレータ―を指定します。
std::ranges::sort(a, a + 9);
std::ranges::sort(std::begin(a), std::end(a));
// std::ranges::xxx(C++20): 対象の範囲を直接指定します。
std::ranges::sort(a);
// コレクション全体だけでなく、イテレーター(ポインター含む)・view を使うことで特定の範囲を渡すことができます。
std::sort(a, std::find(a, a + 9, 1));
std::ranges::sort(std::ranges::subrange(std::begin(a), std::ranges::find(a, 1)));
// vector
std::vector<int> v = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
// std::xxx(C++11): 対象の範囲 [first, last) のイテレータ―を指定します。
std::sort(v.begin(), v.end());
std::sort(std::begin(v), std::end(v));
// std::ranges::xxx(C++20): 対象の範囲 [first, last) のイテレータ―を指定します。
std::ranges::sort(v.begin(), v.end());
std::ranges::sort(std::begin(v), std::end(v));
// std::ranges::xxx(C++20): 対象の範囲を直接指定します。
std::ranges::sort(v);
// コレクション全体だけでなく、イテレーター(ポインター含む)・view を使うことで特定の範囲を渡すことができます。
std::sort(v.begin(), std::find(v.begin(), v.end(), 1));
std::ranges::sort(std::ranges::subrange(v.begin(), std::find(v.begin(), v.end(), 1)));
return 0;
}
戻り値の違い
同名の関数でも std::xxx(C++11) と std::ranges::xxx(C++20) で戻り値が違うものがあります。
#include <algorithm>
#include <iostream>
#include <vector>
int main(int argc, char* argv[]) {
std::cout << "//// 検索" << std::endl;
std::vector<int> v = { 1, 2, 3, 4, 5, 4, 5, 6, 6 };
std::vector<int> k = { 4, 5 };
// C++11: 2つ目の範囲が最初に見つかった位置
auto search11 = std::search(v.begin(), v.end(), k.begin(), k.end());
std::cout << "位置:" << std::distance(v.begin(), search11) << " 値:" << *search11; // 位置:3 値:4
std::cout << std::endl << std::endl;
// C++20: 2つ目の範囲が最初に見つかった範囲
auto search20 = std::ranges::search(v, k);
std::cout << "範囲:[" << std::distance(v.begin(), search20.begin()) << ", " << std::distance(v.begin(), search20.end()) << ") 値:";
for (auto x : search20) { std::cout << x << " "; } // 範囲:[3, 5) 値:4 5
std::cout << std::endl << std::endl;
return 0;
}
関数オブジェクト
比較関数
要素の比較のための関数を指定することができます。
- シーケンシャルアクセスする関数
- 下記以外
- 等しい(==)とき true を返す
- ランダムアクセスする関数
- ソートや二分探索など
- より小さい(<)とき true を返す
#include <algorithm>
#include <iostream>
#include <vector>
struct S1 {
const char* s1;
};
int main(int argc, char* argv[]) {
// 等しいとき true を返す関数を渡す
std::vector<S1> v1 = { {"111"}, {"22"}, {"3"} };
std::vector<S1> k1 = { {"22"}, {"3"} };
auto search = std::ranges::search(v1, k1,
[](auto& a, auto& b) { return std::atoi(a.s1) == std::atoi(b.s1); }); // pred
std::cout << "範囲:[" << std::distance(v1.begin(), search.begin()) << ", " << std::distance(v1.begin(), search.end()) << ") 値:";
for (auto i : search) { std::cout << i.s1 << " "; } // 範囲:[1, 3) 値:22 3
std::cout << std::endl << std::endl;
// より小さいとき true を返す関数を渡す
std::vector<S1> v2 = { {"111"}, {"222"}, {"333"} };
S1 k2 = { "222" };
bool binary_search = std::ranges::binary_search(v2, k2,
[](auto& a, auto& b) { return std::atoi(a.s1) < std::atoi(b.s1); }); // comp
std::cout << "結果:" << std::boolalpha << binary_search << std::endl; // 結果:true
return 0;
}
射影(projection)関数
比較対象を決めるための関数を指定することができます。
#include <algorithm>
#include <iostream>
#include <vector>
struct S1 {
const char* s1;
};
int main(int argc, char* argv[]) {
// 等しいとき true を返す関数を渡す
std::vector<S1> v1 = { {"111"}, {"22"}, {"3"} };
std::vector<S1> k1 = { {"22"}, {"3"} };
auto search = std::ranges::search(v1, k1,
{}, // pred
[](const auto& a) { return std::atoi(a.s1); }, // proj1
[](const auto& b) { return std::atoi(b.s1); }); // proj2
std::cout << "範囲:[" << std::distance(v1.begin(), search.begin())
<< ", " << std::distance(v1.begin(), search.end()) << ") 値:";
for (auto i : search) { std::cout << i.s1 << " "; } // 範囲:[1, 3) 値:22 3
std::cout << std::endl << std::endl;
return 0;
}
関数オブジェクトの指定方法
比較関数、射影(projection)関数などの関数オブジェクトを渡す例です。
#include <algorithm>
#include <iostream>
#include <vector>
struct S1 {
const char* s1;
};
bool comp_s1(const S1& a, const S1& b) {
return std::atoi(a.s1) < std::atoi(b.s1);
}
int main(int argc, char* argv[]) {
std::vector<S1> v1 = { {"111"}, {"22"}, {"3"} };
std::vector<int> v2 = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
// ラムダ関数
std::ranges::sort(v1, [](auto& a, auto& b) { return std::atoi(a.s1) < std::atoi(b.s1); });
// ラムダ関数変数
auto f = [](auto& a, auto& b) { return std::atoi(a.s1) < std::atoi(b.s1); };
std::ranges::sort(v1, f);
// 関数
std::ranges::sort(v1, comp_s1);
// 組み込み関数
std::ranges::sort(v2, std::ranges::greater()); // 大きい順にソート
// 射影(projection)にはメンバー変数ポインターも可
std::ranges::sort(v1, strcmp, &S1::s1); // 辞書順にソート
return 0;
}
演算子オーバーロードによる比較動作の実装
比較関数を指定しない場合、==, < 演算子を使って要素が比較されますが、演算子のオーバーロードで比較動作を実装する例を示します。
なお、< 演算子のオーバーロードはコンパイルが通らず、うまくいかなかったので、代わりに三方演算子 <=> をオーバーロードしています。
#include <algorithm>
#include <iostream>
#include <vector>
class S1 {
public:
const char* s1;
bool operator==(const S1& s) const {
std::cout << "S1::== ";
return std::atoi(this->s1) == std::atoi(s.s1);
}
/*
//binary_search でエラー
//エラー (アクティブ) E0304 オーバーロードされた関数 "std::ranges::_Binary_search_fn::operator()" のインスタンスが引数リストと一致しません
bool operator<(const S1& s) const {
std::cout << "S1::< ";
return std::atoi(this->s1) < std::atoi(s.s1);
}
*/
auto operator<=>(const S1& s) const {
std::cout << "S1::<=> ";
return std::atoi(this->s1) - std::atoi(s.s1);
}
};
class S2 {
public:
const char* s2;
};
bool operator==(const class S2& a, const class S2& b) {
std::cout << "== ";
return std::atoi(a.s2) == std::atoi(b.s2);
}
/*
//binary_search でエラー
//エラー (アクティブ) E0304 オーバーロードされた関数 "std::ranges::_Binary_search_fn::operator()" のインスタンスが引数リストと一致しません
bool operator<(const class S2& a, const class S2& b) {
std::cout << "< ";
return std::atoi(a.s2) < std::atoi(b.s2);
}
*/
auto operator<=>(const class S2& a, const class S2& b) {
std::cout << "<=> ";
return std::atoi(a.s2) - std::atoi(b.s2);
}
int main(int argc, char* argv[]) {
// std::ranges::search
{
// 等しいとき true を返す関数を渡す
std::vector<int> v = { 6, 7, 4, 5, 8, 9, 1, 2, 3 };
std::vector<int> k = { 5, 8 };
auto search = std::ranges::search(v, k, [](int a, int b) { return a == b; });
std::cout << "位置:" << std::distance(v.begin(), search.begin()) << std::endl; // 位置:3
}
{
// オーバーロード operator==
std::vector<S1> v = { {"111"}, {"22"}, {"3"} };
std::vector<S1> k = { {"22"}, {"3"} };
auto search = std::ranges::search(v, k);
std::cout << "位置:" << std::distance(v.begin(), search.begin()) << std::endl; // S1::== S1::== S1::== 位置:1
}
{
// 非メンバー operator==
std::vector<S2> v = { {"111"}, {"22"}, {"3"} };
std::vector<S2> k = { {"22"}, {"3"} };
auto search = std::ranges::search(v, k);
std::cout << "位置:" << std::distance(v.begin(), search.begin()) << std::endl; // == == == 位置:1
}
// std::ranges::binary_search
{
// より小さいとき true を返す関数を渡す
std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
bool binary_search = std::ranges::binary_search(v, 3, [](int a, int b) { return a < b; });
std::cout << "結果:" << std::boolalpha << binary_search << std::endl; // 結果:true
}
{
// オーバーロード operator<=>
std::vector<S1> v = { {"111"}, {"222"}, {"333"} };
S1 k = { "222" };
bool binary_search = std::ranges::binary_search(v, k);
std::cout << "結果:" << std::boolalpha << binary_search << std::endl; // S1::<=> S1::<=> S1::<=> 結果:true
}
{
// 非メンバー operator<=>
std::vector<S2> v = { {"111"}, {"222"}, {"333"} };
S2 k = { "222" };
bool binary_search = std::ranges::binary_search(v, k);
std::cout << "結果:" << std::boolalpha << binary_search << std::endl; // <=> <=> <=> 結果:true
}
return 0;
}
コメント