std::experimental::erase/erase_if
新しい処理系を手に入れてリリースノートに目を通し、その次に僕がやるのはインクルードとライブラリディレクトリの徘徊です。目新しい機能の多くはここらへんに現れますからね。Visual C++ 14のinclude配下に、いかにも怪しげ(?)なディレクトリ:experimentalを見つけました。
<vector>, <list>, <set>などなど、おなじみの標準コンテナたちが集結しています。なにかよほど大きなexperimental(お試し)が行われているのかとエディタで開いてみました。
<list>や<set>などなども同様……どうやら標準コンテナを引数に取るアルゴリズム:erase/erase_ifが定義されているようです。そういえば標準アルゴリズム:<algorithm>に用意されているのはremove/remove_ifであって、"erase"と名の付く関数テンプレートは見当たりません。
英和辞典を引いてみると removeは「除外する」、eraseは「消去する」と書かれていました。意味合いが異なるんですね……なるほど、思い当たるフシがあります。
vector<int>から特定の要素をremoveしてみましょう:
vector<int> container = { 0, 1, 2, 3, 0, 1, 2, 3, 0 }; // containerから0をremoveする auto last = remove(begin(container), end(container), 0); for_each(begin(container), last, [](auto item) { cout << item << ' ';}); cout << endl; cout << "... but actually in vector:" << endl; for (auto item : container) { cout << item << ' '; } cout << endl;
last = remove(p, q, val) は範囲[p,q) からvalと等しい要素を除外し、イテレータ(last)を返します。これにより、範囲[begin(container), last) からはval(この例では0)が消えていますが、containerの要素数に変化はなく、範囲[last, end(container)) にはremove前の要素がそのまま残っています。除外(remove)するけど消去(erase)してはくれません。なのでcontainer内から消去したいなら、lastからcontainer(end)までを明示的に消去しなければなりません。
// 上記に続いて…… cout << "so, erase from last to end." << endl; container.erase(last, end(container)); for (auto item : container) { cout << item << ' '; } cout << endl;
関数removeはコンテナが持つ消去系メンバ関数:erase()やpop_back()などを呼ぶすべがないのだから当然っちゃ当然(でもビギナはよくハマる)なのですが。
set/map系すなわち連想コンテナ(associative-container)からの要素の消去は、さらにややこしくなります:
multiset<int> container = { 0, 1, 2, 3, 0, 1, 2, 3, 0 }; // multiset container から 0 を消去する auto iter = begin(container); while( iter != end(container) ) { if ( *iter == 0 ) { iter = container.erase(iter); } else { ++iter; } } for (auto item : container) { cout << item << ' '; } cout << endl;
「コンテナから特定の値を持つ/条件を満たす要素を消去する」なんて基本的な操作じゃないですか。なのに、こんな面倒なコードを書かにゃならんのです。これを楽にしてくれるのが<experimental>にあるコンテナ・ヘッダに追加されたstd::experimental::erase/erase_ifです。
erase/erase_ifの第一引数にはイテレータではなく、コンテナそのもの(参照)を与えます。コンテナそのものを引き渡すので、コンテナのメンバ関数eraseを呼び出して消去できるってカラクリですね。
vector<int> container = { 0, 1, 2, 3, 0, 1, 2, 3, 0 }; std::experimental::erase(container, 0); // containerから0を消去する // ……消えたかな? for (auto item : container) { cout << item << ' '; } cout << endl;
この拡張eraseがサポートされるのは標準コンテナ:
- deque
- forward_list
- list
- map / multimap
- set / multiset
- basic_string (string/wstring)
- unordered_map / unordered_multimap
- unordered_set / unordered_multiset
- vector
このうち、set/map系については(eraseはメンバ関数に定義済みなので)erase_ifのみ追加されています。
using namespace std; map<string, string> container = { { "りんご", "apple" }, { "みかん", "orange" }, { "ぶどう", "grape" }, { "いちご", "strawberry" }, { "バナナ", "banana" }, }; // 和英辞典(container)から、英単語に'p'を含む要素を消去する experimental::erase_if(container, [](auto item) { return item.second.find('p') != string::npos; }); // ……消えたかな? for (auto item : container) { cout << item.first << ':' << item.second << endl; } cout << endl;