std::range算法簡介。
- range算法被定義在<algorithm>頭中,而range的基礎結構和核心類型被定義在<ranges>頭中。
- 通常,range算法至少有兩個重載:帶有一對疊代器的重載和帶有單一範圍參數的重載。
- range版本采取 "投影",這有時允許更多的靈活性;例如,你可以針對一些標明的成員進行排序,或者在比較之前執行額外的轉換。
- range版本沒有并行執行選項(你不能傳遞std::執行政策)。
- range算法,與C++20的标準算法類似,也是constexpr。
- 從C++20開始,沒有對應于<numeric>頭的range數值算法。
下面,你可以找到顯示标準算法和帶有range的替代版本的例子。它們說明了一些基本概念,并盡量不使用進階的range組成或視圖。我們将按照在cppreference/algorithms找到的順序進行,在這一部分,我們将介紹 "非修改序列操作"。
1. all_of, any_of, none_of
标準算法
#include <algorithm>
#include <vector>
#include <iostream>
#include <ranges>
int main() {
const std::vector nums = {1, 2, 3, -4, 5, 6, 7, 8 };
auto is_positive = [](const auto& v) { return v > 0; };
// standard version:
auto res = std::all_of(begin(nums), end(nums), is_positive);
std::cout << "std::all_of: " << res << '\n';
res = std::any_of(begin(nums), end(nums), is_positive);
std::cout << "std::any_of: " << res << '\n';
}
range版本
// ranges version:
res = std::ranges::all_of(nums, is_positive);
std::cout << "std::ranges::all_of: " << res << '\n';
res = std::ranges::any_of(nums, is_positive);
std::cout << "std::ranges::any_of: " << res << '\n';
我們還可以寫一個更複雜的例子,掃描一個自定義類型的容器。
#include <algorithm>
#include <vector>
#include <iostream>
#include <ranges>
struct Product {
std::string name_;
double value_ { 0.0 };
};
int main() {
const std::vector<Product> prods {
{ "box", 10.0 }, {"tv", 100.0}, {"none", -1.0}
};
auto is_positive = [](const auto& v) { return v > 0; };
auto is_positive_val = [](const Product& p) {
return p.value_ > 0;
};
// standard version:
auto res = std::all_of(begin(prods), end(prods), is_positive_val);
std::cout << "std::all_of: " << res << '\n';
res = std::any_of(begin(prods), end(prods), is_positive_val);
std::cout << "std::any_of: " << res << '\n';
// ranges version:
res = std::ranges::all_of(prods, is_positive, &Product::value_);
std::cout << "std::ranges::all_of: " << res << '\n';
res = std::ranges::any_of(prods, is_positive, &Product::value_);
std::cout << "std::ranges::any_of: " << res << '\n';
}
在range版本中,我們仍然可以使用is_positive,一個通用的謂詞,但我使用了一個投影,它隻 "接受 "Product::value_并将其傳入謂詞。在标準情況下,我不得不寫一個自定義的lambda來識别Product的類型。
2. for_each
一個基于範圍的for循環的替代方案。
#include <algorithm>
#include <vector>
#include <iostream>
#include <ranges>
struct Product {
std::string name_;
double value_ { 0.0 };
};
int main() {
const std::vector<Product> prods {
{ "box", 10.0 }, {"tv", 100.0}, {"none", -1.0}
};
auto out = [](const auto& v) { std::cout << v << ", "; };
// standard version:
std::cout << "std::for_each: \n";
std::for_each(begin(prods), end(prods), [](const Product& p){
std::cout << p.name_ << ", " << p.value_ << '\n';
});
std::cout << "std::for_each only names reverse: \n";
std::for_each(rbegin(prods), rend(prods), [](const Product& p){
std::cout << p.name_ << '\n';
});
// ranges version:
std::cout << "std::ranges::for_each: \n";
std::ranges::for_each(prods, [](const Product& p) {
std::cout << p.name_ << ", " << p.value_ << '\n';
});
std::cout << "std::ranges::for_each only names in reverse: \n";
std::ranges::for_each(prods | std::views::reverse,
out, &Product::name_);
}
令人振奮的是,在标準版本中以反向順序列印需要使用rbegin/rend疊代器,然後使用一個自定義的單選函數來列印來自Product類的确切資料成員。而對于range,我們可以應用views::reverse,使用一個簡單的輸出函數,然後是一個投影。
缺少的是range算法的并行版本。
// standard:
std::for_each(std::execution::par, begin(prods), end(prods), /*...*/);
// no ranges version...
// std::ranges::for_each(std::execution::par, prods, /*... */); // doesn't compile...
所有的range算法都缺少并行版本,而不僅僅是for_each。
3. count_if
在下面的例子中,我們将計算名稱以 "no "開頭的Product。
#include <algorithm>
#include <vector>
#include <iostream>
#include <ranges>
struct Product {
std::string name_;
double value_ { 0.0 };
};
int main() {
const std::vector<Product> prods {
{ "box", 10.0 }, {"tv", 100.0}, {"none", -1.0},
{ "car", 1000.0 }, {"toy", 40.0}, {"none", 0.0}
};
// standard version:
auto res = std::count_if(begin(prods), end(prods), [](const Product& p){
return p.name_.starts_with("no");
});
std::cout << "std::count_if: " << res << '\n';
// ranges version:
res = std::ranges::count_if(prods, [](const Product& p) {
return p.name_.starts_with("no");
});
std::cout << "std::ranges::count_if: " << res << '\n';
// alternative version for "none":
res = std::ranges::count(prods, std::string{"none"}, &Product::name_);
std::cout << "std::ranges::count: " << res << '\n';
}
這個例子顯示了三種方法,最後一種使用了一個投影,隻檢查Product::name_資料成員。在這個方法中,我們精确地搜尋 "none",是以它比starts_with更嚴格。
4. find_if
到目前為止,我們的文本算法都是傳回布爾值或積分值,但有了find*函數,我們就有了顯示更多内容的疊代器(或子range)。
請看這個例子。
#include <algorithm>
#include <vector>
#include <iostream>
#include <ranges>
struct Product {
std::string name_;
double value_ { 0.0 };
};
int main() {
const std::vector<Product> prods {
{ "box", 10.0 }, {"tv", 100.0}, {"rocket", 10000.0},
{ "car", 1000.0 }, {"toy", 40.0}, {"none", 0.0}
};
// standard version:
auto it = std::find_if(begin(prods), end(prods), [](const Product& p){
return p.name_.starts_with("ro");
});
if (it != end(prods))
std::cout << "std::find_if: " << it->name_ << '\n';
// ranges version:
auto res = std::ranges::find_if(prods, [](const Product& p) {
return p.name_.starts_with("ro");
});
if (res != end(prods))
std::cout << "std::ranges::find_if: " << res->name_ << '\n';
}
像其他算法一樣,也有一個 "正常 "版本,你可以傳遞兩個疊代器。
it = std::ranges::find_if(begin(prods), end(prods), [](const Product& p) {
return p.name_.starts_with("ro");
});
采取單一範圍的版本是特殊的,因為它傳回一個借用的疊代器。這種特殊類型允許檢查臨時/生命期對象的問題。當你傳遞兩個疊代器時,這是不可能的(因為容器在某處存在),但用一個臨時範圍就可以。
struct Product {
std::string name_;
double value_ { 0.0 };
};
std::vector<Product> GetProds() {
return {
{ "box", 10.0 }, {"tv", 100.0}, {"rocket", 10000.0},
{ "car", 1000.0 }, {"toy", 40.0}, {"none", 0.0}
};
}
int main() {
auto it = std::ranges::find_if(GetProds(), [](const Product& p) {
return p.name_.starts_with("ro");
});
std::cout << "std::ranges::find_if: " << it->name_ << '\n';
}
這不能編譯,你會看到以下錯誤。
error: base operand of '->' has non-pointer type 'std::ranges::dangling'
22 | std::cout << "std::ranges::find_if: " << it->name_ << '\n';
| ^~
正如你所看到的,編譯器檢查了GetProds()傳回一個臨時的值,而我們會發現的疊代器是懸空的。
5. find_first_of
讓我們來看看另一個find*函數的替代方案,它可以一次搜尋多個元素。
#include <algorithm>
#include <vector>
#include <iostream>
#include <ranges>
struct Product {
std::string name_;
double value_ { 0.0 };
friend bool operator==(const Product& a, const Product& b) {
return a.name_ == b.name_ && abs(a.value_ - b.value_) < 0.0001;
}
};
int main() {
const std::vector<Product> prods {
{ "box", 10.0 }, {"default", 0.0 }, {"tv", 100.0}, {"rocket", 10000.0},
{ "car", 1000.0 }, {"toy", 40.0}, {"none", 0.0 }, { "ball", 40.0 }
};
const std::vector<Product> invalids {
{"default", 0.0 }, {"none", 0.0 }
};
// standard version:
auto it = std::find_first_of(begin(prods), end(prods), begin(invalids), end(invalids));
if (it != end(prods)) {
std::cout << "std::find_first_of: " << it->name_ << " at: "
<< std::distance(begin(prods), it) <<'\n';
auto it2 = std::find_first_of(std::next(it), end(prods), begin(invalids), end(invalids));
if (it2 != end(prods))
std::cout << "std::find_first_of: " << it2->name_ << " at: "
<< std::distance(begin(prods), it2) <<'\n';
}
// ranges version:
const std::array<std::string, 2> arrInvalids{"default", "none"};
auto res = std::ranges::find_first_of(prods, arrInvalids,
std::ranges::equal_to{}, &Product::name_);
if (res != end(prods)) {
const auto pos = std::distance(begin(prods), res);
std::cout << "std::ranges::find_first_of: " << res->name_
<< " at: " << pos <<'\n';
auto res2 = std::ranges::find_first_of(prods | std::views::drop(pos+1), arrInvalids,
std::ranges::equal_to{}, &Product::name_);
if (res2 != end(prods)) {
std::cout << "std::ranges::find_first_of: " << res2->name_
<< " at: " << std::distance(begin(prods), res2) <<'\n';
}
}
}
std::find_first_of需要兩對疊代器。我想在例子中的prod序列中找到 "無效的 "product。因為我在比較product,是以我必須為我的結構定義operator==。另外,我可以提供一個二進制操作,然後隻比較名稱。
auto cmpNames = [](const Product& a, const Product& b) {
return a.name_ == b.name_;
};
auto it = std::find_first_of(begin(prods), end(prods),
begin(invalids), end(invalids), cmpNames);
if (it != end(prods)) {
// ...
}
在range版中,我可以使用預測和預設比較器來達到類似的效果。
const std::array<std::string, 2> arrInvalids{"default", "none"};
auto res = std::ranges::find_first_of(prods, arrInvalids,
std::ranges::equal_to{}, &Product::name_);
後來有趣的部分是,對于第二次搜尋,我可以使用drop來跳過範圍中的前N個元素。
auto res2 = std::ranges::find_first_of(prods | std::views::drop(pos+1),
arrInvalids, std::ranges::equal_to{}, &Product::name_);
另外,你也可以使用一個有兩對疊代器的版本。
auto res2 = std::ranges::find_first_of(std::next(res), end(prods),
begin(arrInvalids), end(arrInvalids),
std::ranges::equal_to{}, &Product::name_);
6. mismatch
通過不比對算法,我們可以找到兩個範圍不同的第一個地方。
#include <algorithm>
#include <vector>
#include <iostream>
#include <ranges>
#include <iomanip> // quoted
int main() {
const std::string firstStr = "Hello Super World";
const std::string secondStr = "Hello Amazing World";
std::cout << "mismatch for " << std::quoted(firstStr)
<< " and " << std::quoted(secondStr) << '\n';
// standard version:
auto [first, second] = std::mismatch(begin(firstStr), end(firstStr), begin(secondStr));
{
const auto pos = std::distance(begin(firstStr), first);
std::cout << "std::mismatch: at pos " << pos << '\n';
}
// ranges version:
auto res = std::ranges::mismatch(firstStr, secondStr);
{
const auto pos = std::distance(begin(firstStr), res.in1);
std::cout << "std::ranges::mismatch: at pos " << pos << '\n';
}
}
range版本傳回:
template<class I1, class I2>
using mismatch_result = ranges::in_in_result<I1, I2>;
這是一對兩個疊代器,但我們可以通過.in1和.in2通路它們。
為什麼不是一個簡單的範圍呢?在cpp參考中我們可以看到下面這句話。
Unlike std::pair and std::tuple, this class template has data members of meaningful names.
其結果在結構化綁定時表現不錯,是以你可以寫。
auto [n1, n2] = std::ranges::mismatch(firstStr, secondStr);
const auto pos = std::distance(begin(firstStr), n1);
std::cout << "std::ranges::mismatch: at pos " << pos << '\n';
代碼幾乎與标準版相同。
7. search
在另一個範圍/容器中搜尋模式。
#include <algorithm>
#include <vector>
#include <iostream>
#include <ranges>
#include <functional> // searchers
#include <iomanip>
int main() {
const std::string testString = "Hello Super World";
const std::string needle = "Super";
std::cout << "looking for " << std::quoted(needle)
<< " in " << std::quoted(testString) << '\n';
// standard version:
auto it = std::search(testString.begin(), testString.end(),
std::boyer_moore_searcher(needle.begin(), needle.end()));
if (it != testString.end()) {
const auto pos = std::distance(testString.begin(), it);
std::cout << "std::search: found at pos " << pos << '\n';
}
// ranges version:
auto res = std::ranges::search(testString, needle);
if (!res.empty()) {
const auto first = std::distance(testString.begin(), res.begin());
const auto last = std::distance(testString.begin(), res.end());
std::cout << "std::ranges::search: found between "
<< first << " and " << last << '\n';
}
}
标準版本傳回第二個字元串開始的第一個字元串的疊代器(如果不在那裡,則傳回end())。而range版本則傳回一個子range(或一個借用的子range)。
我們也可以使用投影以不區分大小寫的方式進行檢查。
// ranges version:
const std::string testString2 = "hello abc world";
const std::string needle2 = "ABC";
std::cout << "looking for " << std::quoted(needle2) << " in "
<< std::quoted(testString2) << '\n';
res = std::ranges::search(testString2, needle2,
std::ranges::equal_to{}, ::toupper, ::toupper);
if (!res.empty())
{
const auto first = std::distance(testString2.begin(), res.begin());
const auto last = std::distance(testString2.begin(), res.end());
std::cout << "std::ranges::search: found between "
<< first << " and " << last << '\n';
}
另一個函數 ranges::search_n可以友善地在輸入範圍内找到一個給定值的N個出現次數。
#include <algorithm>
#include <iostream>
#include <ranges>
#include <iomanip>
int main() {
const std::string sequence = "CTGCCCAGGGTTT";
const char letter = 'C';
const size_t count = 3;
std::cout << "looking for " << count << " "
<< letter << "'s in " << std::quoted(sequence) << '\n';
// standard version:
auto it = std::search_n(begin(sequence), end(sequence), count, letter);
if (it != end(sequence))
{
const auto pos = std::distance(begin(sequence), it);
std::cout << "std::search_n: found at pos " << pos << '\n';
}
// ranges version:
auto res = std::ranges::search_n(sequence, count, letter);
if (!res.empty())
{
const auto first = std::distance(begin(sequence), res.begin());
const auto last = std::distance(begin(sequence), res.end());
std::cout << "std::ranges::search_n: found between "
<< first << " and " << last << '\n';
}
}
在标準版本中,沒有特殊的搜尋器;你隻能使用并行算法來調用它。
總結
在這篇文章中,我們涵蓋了非修改性操作類别中的七種不同的算法 "類型":檢查所有/無/部分元素的一些謂詞,搜尋,查找,一般疊代。總共有超過10個不同的例子。
ranges算法提供了一種更簡單的方法來傳遞 "整個 "容器--隻有一個參數,而不是傳遞給疊代器。它們也允許投影,并且有辦法檢測到臨時範圍的疊代器。它們也有局限性,比如缺乏進階搜尋器或并行執行模式。