作为这个问题算法的核心(正如在the accepted answer 中结合std::find_if 和std::none_of 优雅地涵盖的那样),在失败时短路,是扫描容器以查找一元谓词和,当遇到时,继续扫描容器的其余部分以查找谓词的 否定,我还会提到 C++17 中引入的否定符 std::not_fn,替换不太有用的 std::not1 和 @ 987654331@ 构造。
我们可以使用std::not_fn 来实现与接受的答案相同的谓词逻辑(std::find_if 有条件地后跟std::none_of),但语义有所不同,将后面的步骤(std::none_of)替换为std::all_of超过第一步中使用的一元谓词的否定 (std::find_if)。例如:
// C++17
#include <algorithm> // std::find_if
#include <functional> // std::not_fn
#include <ios> // std::boolalpha
#include <iostream>
#include <iterator> // std::next
#include <vector>
template <class InputIt, class UnaryPredicate>
constexpr bool one_of(InputIt first, InputIt last, UnaryPredicate p) {
auto it = std::find_if(first, last, p);
return (it != last) && std::all_of(std::next(it), last, std::not_fn(p));
}
int main() {
const std::vector<int> v{1, 3, 5, 6, 7};
std::cout << std::boolalpha << "Exactly one even number : "
<< one_of(v.begin(), v.end(), [](const int n) {
return n % 2 == 0;
}); // Exactly one even number : true
}
静态尺寸容器的参数包方法
由于我已经将此答案限制为 C++14(及更高版本),因此我将包含一种用于静态大小容器的替代方法(这里特别适用于 std::array),结合使用 std::index_sequence带参数包扩展:
#include <array>
#include <ios> // std::boolalpha
#include <iostream>
#include <utility> // std::(make_)index_sequence
namespace detail {
template <typename Array, typename UnaryPredicate, std::size_t... I>
bool one_of_impl(const Array& arr, const UnaryPredicate& p,
std::index_sequence<I...>) {
bool found = false;
auto keep_searching = [&](const int n){
const bool p_res = found != p(n);
found = found || p_res;
return !found || p_res;
};
return (keep_searching(arr[I]) && ...) && found;
}
} // namespace detail
template <typename T, typename UnaryPredicate, std::size_t N,
typename Indices = std::make_index_sequence<N>>
auto one_of(const std::array<T, N>& arr,
const UnaryPredicate& p) {
return detail::one_of_impl(arr, p, Indices{});
}
int main() {
const std::array<int, 5> a{1, 3, 5, 6, 7};
std::cout << std::boolalpha << "Exactly one even number : "
<< one_of(a, [](const int n) {
return n % 2 == 0;
}); // Exactly one even number : true
}
这也会在早期失败时短路(“发现不止一个”),但将包含比上述方法更简单的布尔比较。
然而,请注意,这种方法可能有其缺点,特别是对于具有许多元素的容器输入的优化代码,正如@PeterCordes 在下面的评论中指出的那样。引用评论(因为不能保证 cmets 会随着时间的推移而持续存在):
仅仅因为大小是静态的并不意味着使用模板完全展开循环是个好主意。在生成的 asm 中,每次迭代都需要一个分支才能在找到时停止,所以这也可能是一个循环分支。 CPU 擅长运行循环(代码缓存、环回缓冲区)。编译器将根据启发式完全展开静态大小的循环,但如果a 很大,则可能不会回滚。所以你的第一个 one_of 实现已经两全其美了,假设一个普通的现代编译器,比如 gcc 或 clang,或者可能是 MSVC