Rendre les boucles for C ++ 11 basées sur des plages un peu plus utiles

C ++ 11 est génial. Lune des plus belles fonctionnalités (à mon avis) est probablement la soi-disant boucle basée sur la plage. Au lieu de

for ( std::size_t i(0); i < range.size(); ++i ) { // do something to range[i]; } 

ou

for ( Range::iterator it(range.begin()); it != range.end(); ++it ) { // do something to *it; } 

ou simplifié avec C ++ 11 auto:

for ( auto it(range.begin()); it != range.end(); ++it ) { // do something to *it; } 

nous pouvons dire ceci:

for ( auto& entry : range ) { // do something with entry. } 

Cest très expressif: nous parlons à propos de lentrée, plutôt que la i ^ ème position dans la plage ou litérateur pointant vers une entrée. Cependant, cette syntaxe na pas la capacité davoir des sous-plages (par exemple en ignorant la dernière entrée). Dans une série de petites structures / méthodes daide, je veux ajouter cette fonctionnalité dune manière propre.

Voilà pour ma motivation 😉 Maintenant, pour la vraie affaire.

Dans ce post, jaborde les conditions sur les entrées de range. Fondamentalement, le code dassistance devrait exécuter léquivalent de

for ( auto& entry : range ) { if ( condition(entry) ) { // do something to entry. } } 

mais sans ce niveau dindentation.

Voici le code pour ConditionalRange:

template <typename Range, typename Runnable> ConditionalRange<Range, Runnable> makeConditionalRange(Range& range, Runnable&& condition) { static_assert(std::is_same<decltype(condition(*std::declval<Range>().begin())), bool>::value, "Condition must return a boolean value."); return ConditionalRange<Range, Runnable>(range, std::forward<Runnable>(condition)); } template <typename Range, typename Runnable> struct ConditionalRange { public: friend ConditionalRange makeConditionalRange<>(Range&, Runnable&&); public: using iterator_type = ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>; iterator_type begin() const { auto b = range.begin(); while ( b != range.end() && !condition(*b) ) { ++b; } return ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>(b, range.end(), condition); } iterator_type end() const { return ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>(range.end(), range.end(), condition); } private: ConditionalRange(Range& range_, Runnable&& condition_) : range(range_), condition(std::forward<Runnable>(condition_)) { } private: Range& range; Runnable condition; }; 

Voici la structure daide de litérateur:

template <typename Iterator, typename Runnable> struct ConditionalIterator { private: Iterator iterator; Iterator end; const Runnable& condition; public: ConditionalIterator(Iterator b, Iterator e, const Runnable& r) : iterator(b), end(e), condition(r) { } auto operator*() -> decltype(*iterator) { return *iterator; } ConditionalIterator& operator++() { do { ++iterator; } while ( iterator != end && !condition(*iterator) ); return *this; } bool operator==(const ConditionalIterator& second) { return iterator == second.iterator; } bool operator!=(const ConditionalIterator& second) { return !(*this == second); } }; 

Lutilisation prévue est:

std::vector<int> ns{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; for ( const auto& n : makeConditionalRange(ns, [](int a) { return a % 2 == 0; }) ) { std::cout << n << " "; } std::cout << "\n"; 

Démo: cliquez ici!

Quoi sont des faiblesses de mon approche? Avec quel type darguments échoue-t-il, même si cela ne devrait pas être dans le monde parfait?

Commentaires

  • Je ne vois aucune faiblesse, mais je pensez que vous devriez envisager de renommer makeConditionalRange avec quelque chose qui se concentre sur lapparence du code client, pas sur " créer une plage conditionnelle ". Par là, je signifie que le code client doit plutôt ressembler à ceci: for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
  • Lintention ici diffère-t-elle significativement dun Boost filter_iterator ?
  • @JerryCoffin Non, ' t. Je nai simplement pas ' Jai examiné Boost 😉
  • Le test le plus courant pour les itérateurs operator!= optimise cela sur operator==
  • @stefan: Pour être franc, ' est idiot. Cest comme dire que je ne ' t nachètera pas une voiture des fabricants standard parce que je veux en construire un qui coule moi-même dalgues pour garantir quil ne cause pas de pollution. Le problème est que votre ensemble de bibliothèques va être infiniment plus bogué que boost parce que vous navez quun seul regard pour les regarder. Boost a des milliers de personnes qui vérifient et valident la correction du code et fournissent des commentaires pour sassurer que les idiomes corrects sont utilisés correctement. Lire: stackoverflow.com/a/149305/14065

Réponse

Vous pouvez utiliser des accolades pour revenir de vos fonctions. De cette façon, vous naurez pas à répéter des types longs et encombrants (DRY, autant que possible). Par exemple, ConditionalRange::end:

iterator_type end() const { return { range.end(), range.end(), condition }; } 

En outre, vous souhaitez probablement que votre fonction accepte également les tableaux de style C. Par conséquent, vous devez utiliser std::begin et std::end au lieu dappeler les fonctions membres:

iterator_type end() const { return { std::end(range), std::end(range), condition }; } 

Avec cela (partout dans votre code) et quelques changements triviaux, votre code fonctionnera également pour C- tableaux de style (testés ici et cela fonctionne bien).

Nommer votre fonction

Le nom makeConditionalRange est assez long. La fonction que vous avez créée existe déjà dans dautres bibliothèques et langages de programmation (Python me vient à lesprit) sous le nom filter. Vous devriez considérer changer son nom pour que de nombreux utilisateurs reconnaissent le nom au premier coup dœil (de plus, il sera plus court).

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *