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;
}
  
  
  
  

コメント