假设我有两个向量(原因与我正在使用的框架有关):
std::vector<bool> valid = {false, true, true, false};
std::vector<int> percentages = {2, 50, 3, 100};
这些向量代表电池的有效性和充电百分比。我想找到有效电池的最小充电量。要用范围来做这件事,我能想到的最好的是:
auto min = std::get<1>(
std::ranges::min(
std::views::zip(valid, percentages) |
std::views::filter([](const auto& vals) {return std::get<0>(vals);}),
[](const auto& vals0, const auto& vals1) {
return std::get<1>(vals0) < std::get<1>(vals1);
}));
现在是this works,但是几乎不可能读取,所以for循环的变体应该是:
int min_percentage = 100;
for (const auto& [is_valid, percentage] : std::views::zip(valid, percentages)) {
if (is_valid) {
min_percentage = std::min(min_percentage, percentage);
}
}
哪个works just fine as well。
第二种方法客观上更好,所以问题是,有没有一种方法可以在保持代码可读性的同时用范围来编写它?
4条答案
按热度按时间hts6caw31#
一般来说,这是可读性差的原因:
这是一个错误的算法--你想找到最小的 percentage,但是你现在的算法是按百分比找到最小的 * 对(valid,percentage)*。
简单地重构到正确的算法(最小百分比)是一个很大的改进:
现在有一个提议(P2769),让你把lambda写成
std::ranges::get_key
(或者std::ranges::get_element<0>
)。还要注意,传入
min
的 predicate 并不是绝对必要的--默认的<
已经做了正确的事情,因为该范围内的所有元组现在都是(true, x)
,所以根据定义,那里的最小值是x
最小的值。这是其中一种情况,就像我们缺少了一个算法:
其中,
filter_map
取T -> optional<U>
,并生成范围U
。不过,在这种情况下,你可以作弊--你用
for
循环编写的算法可以用transform
来完成:你把无效电池Map到100:或者更直接地说:
这在没有有效电池的情况下有不同的语义(你得到一个定义良好的
100
而不是UB)。尽管你的循环即使在没有电池的情况下也是定义良好的(你得到100
),而在这里空的情况下仍然是UB。omjgkv6w2#
我觉得如果你给这些羊起个名字的话是很容易理解的:
https://godbolt.org/z/95T8xsq68
pgpifvop3#
关于:
同样,您可以使用
rv::elements<1>
代替rv::values
。xzlaal3s4#
理想情况下,我们可以只传递
std::get<0>
作为过滤函数。不幸的是,编译器无法推导出重载,所以我们必须用lambda来帮助它。由于这是一个常见的问题(How do I specify a pointer to an overloaded function?),在您的项目中为它提供一个帮助宏可能是合理的:
这使得代码看起来如下所示:
https://godbolt.org/z/Gaa1ErdGG