Utilisation de std::not_fn
pour nier un prédicat
Comme le cœur de l'algorithme de cette question (comme cela a été élégamment couvert par la combinaison de std::find_if
y std::none_of
sur la réponse acceptée ), avec court-circuit en cas d'échec, consiste à analyser un conteneur à la recherche d'un prédicat unaire et, lorsqu'il est satisfait, à poursuivre l'analyse du reste du conteneur à la recherche de la négation du prédicat, je mentionnerai aussi le négateur std::not_fn
introduit dans C++17, remplaçant le moins utile std::not1
y std::not2
les constructions.
Nous pouvons utiliser std::not_fn
pour implémenter la même logique de prédicat que la réponse acceptée ( std::find_if
suivi sous condition par std::none_of
), mais avec une sémantique quelque peu différente, en remplaçant la dernière étape ( std::none_of
) avec std::all_of
sur le négation du prédicat unaire utilisé dans la première étape ( std::find_if
). Par exemple :
// 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
}
Une approche de type "parameter pack" pour les conteneurs de taille statique
Comme j'ai déjà limité cette réponse à C++14 (et au-delà), je vais inclure une approche alternative pour les conteneurs de taille statique (ici appliquée pour std::array
spécifiquement), en utilisant std::index_sequence
combiné à l'expansion du pack de paramètres :
#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
}
Cela court-circuite également les échecs précoces ("trouvé plus d'un"), mais contient quelques comparaisons booléennes plus simples que dans l'approche ci-dessus.
Cependant Dans le cadre de l'utilisation de l'outil de gestion de l'information, notez que cette approche peut avoir des inconvénients, en particulier pour l'optimisation du code pour les entrées de conteneurs avec de nombreux éléments, comme le souligne @PeterCordes dans un commentaire ci-dessous. Citer le commentaire (car les commentaires ne sont pas garantis de persister dans le temps) :
Ce n'est pas parce que la taille est statique que le fait de dérouler entièrement la boucle avec des modèles est une bonne idée. Dans l'asm résultant, cela nécessite une branche à chaque itération de toute façon pour s'arrêter sur found, donc cela pourrait aussi bien être une branche de boucle. Les processeurs sont bons pour exécuter des boucles (caches de code, tampons de bouclage). Les compilateurs dérouleront complètement les boucles de taille statique en se basant sur des heuristiques, mais il est probable qu'ils ne le fassent pas. ne le fera pas Rembobinez si a
est énorme. Donc votre première one_of
a déjà le meilleur des deux mondes, en supposant qu'un compilateur moderne normal comme gcc ou clang, ou peut-être MSVC