作为这个问题算法的核心,它通过聚合std::find_if
和std::none_of
在被接受的答案中优雅地覆盖,并在失败时进行短路。扫描容器以查找一元谓词,并在满足条件时,继续扫描余下的容器来寻找该谓词的取反结果。我还会提到 C++17 中引入的 negator std::not_fn
,替代了不太有用的std::not1
和std::not2
构造。
我们可以使用std::not_fn
来实现与被接受的答案相同的谓词逻辑(std::find_if
条件性跟随 std::none_of
),但是语义略有不同,将后者(std::none_of
) 替换为应用于第一步(std::find_if
)所使用的一元谓词的取反结果的std::all_of
。例如:
#include <algorithm>
#include <functional>
#include <ios>
#include <iostream>
#include <iterator>
#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;
});
}
一种适用于静态大小容器的参数包方法
既然我已经将本答案限定为C++14(及更高版本),我将介绍一种针对静态大小容器的另一种方法(特别是应用于std::array
),利用std::index_sequence
与参数包展开:
#include <array>
#include <ios>
#include <iostream>
#include <utility>
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;
}
}
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;
});
}
这种方法也会在早期失败(“发现多个”)时进行短路,但是与上面的方法相比,它将包含更多简单的布尔比较。
然而,请注意,这种方法可能有其缺点,特别是针对具有许多元素的容器输入的优化代码,正如@PeterCordes在下面的评论中指出的那样。引用评论(由于评论不能保证随时间推移而存在):
仅因为大小是静态的并不意味着使用模板完全展开循环是一个好主意。在生成的汇编代码中,这需要每次迭代都执行一个分支来停止查找,因此这可能就是一个循环分支。CPU擅长运行循环(代码缓存,回路缓冲区)。编译器将根据启发式算法完全展开基于静态大小的循环,但如果a
很大,则可能不会将其回滚。因此,您的第一个one_of
实现已经拥有最佳的两个世界,假设使用像gcc或clang或者可能是MSVC这样的普通现代编译器。
std::any_of()
函数返回至少有1个元素满足谓词条件,但可能不止一个。而std::any_of()
并不会返回恰好只有1个元素满足谓词条件,这正是OP所需要的。 - Remy Lebeau