你的标题与问题不符
你的标题问道“是否应该使用范围(Ranges)?”但在你的问题中,你提到正在考虑使用range-v3 —— 那么你应该使用range-v3还是C++20的范围(Ranges)呢?
这就像是问“是否应该使用ASIO?”然后又表示你正在在Boost.ASIO和独立ASIO之间做选择。如果一个人正在在这些选项之间做选择,那么显然已经决定“总体上使用ASIO”了,不是吗?所以在你的情况下,你似乎已经决定“总体上使用范围(Ranges)”,现在我们只是在讨论价格问题。
因此,我的回答可能并不针对你,而是针对那些想知道是否将C++20的范围(Ranges)引入到尚未基于范围(Ranges)构建的代码库中的假设读者。
不能避免范围(Ranges);或者说,“你无法避免范围(Ranges)。”
这真的取决于你将如何使用范围。在我看来,现在已经相当明显,“将范围视图化”普通的业务逻辑代码是一个不好的主意,无论是从可理解性还是性能方面来说。例如,请不要改变。
for (int i : selected_indices) {
if (products[i].price > 10) {
std::cout << products[i].name;
}
}
进入
std::ranges::copy(
selected_indices
| std::views::filter([](auto& p) { return p.price > 10; })
| std::transform(&Product::name),
std::ostream_iterator<std::string_view>(std::cout)
);
然而,改变是完全合理的。
int expected[] = {1,2,3,4,5};
EXPECT_TRUE(std::equal(actual.begin(), actual.end(), expected, expected+5));
进入
int expected[] = {1,2,3,4,5};
EXPECT_TRUE(std::ranges::equal(actual, expected);
这也是"C++20 Ranges code"。但这次它实际上提高了代码的可读性。(它仍然增加了编译时成本,但运行时成本保持不变。)
此外,如果您已经通过泛型编程使用C++98-STL风格的"算法",那么您应该绝对采用C++20对迭代器模型的修改,以便您的算法既适用于旧式迭代器又适用于新式迭代器。换句话说,我认为重写一个形式如下的实用程序库可能是值得的。
template<class It, class Pred>
bool my::is_uniqued(It first, It last, Pred pred) {
for (auto it = first; it != last; ++it) {
if (pred(*first, *std::next(first))) {
return false;
}
}
return true;
}
转换成更适合C++20的形式,比如
template<class It, class Sent, class Pred>
bool my::is_uniqued(It first, Sent last, Pred pred) {
for (auto it = first; it != last; ++it) {
if (pred(*first, *std::next(first))) {
return false;
}
}
return true;
}
template<std::ranges::range R>
bool my::is_uniqued(R&& rg) {
return my::is_uniqued(rg.begin(), rg.end());
}
这有点像当你更新一个接受
const string&
的函数,改为接受
string_view
,从而允许它接受更多种类的类似字符串的参数。我们正在更新
is_uniqued
函数,使其能够接受更多种类的可迭代范围参数。这可以被视为对"代码卫生"的好处。
在这个例子中,我想不出任何特定的理由来开始使用
std::ranges::next
来替代
std::next
;而且你可能
不应该添加像这样的约束。
template<std::forward_iterator It, std::sentinel_for<It> Sent, std::predicate<std::iter_reference_t<It>> Pred>
bool my::is_uniqued(It first, Sent last, Pred pred) {
因为那样只会浪费你的编译时间,以及在出现问题时产生的编译器诊断信息。它还存在使你的模板对某些现有手写的C++98迭代器类型不可用的风险,这些类型未能满足
std::forward_iterator
的某些细节要求。(根据我的经验,最常见的情况是有人忘记给其
operator*()
加上const修饰符。我认为这样的迭代器类型有缺陷,值得修复,但你可能没有时间或权限立即去修复它。)这也会引发无关紧要的争论:“为什么你写了
std::iter_reference_t
而不是
std::iter_value_t
?我们是否应该对两者都进行约束?”制定一个总体的风格规则,“我们不会不必要地限制我们的模板”,可以避免很多无谓的争论。
另一方面,如果你正在开发一个目前需要手写大量
enable_if
来“模拟”Ranges本身所具备的功能的库... 那么,使用C++20的Ranges当然会更有益处!
template<typename Container, typename Range> Container range_to(Range&&);
也并不难。 - Calethstd
命名空间中。而且,一些C++23功能无法在不改变现有基础设施的情况下工作。你可以实现一些像“视图到容器”的附加功能,但仅限于此。 - Nicol Bolas