web-dev-qa-db-fra.com

C++ 11 basé sur l'intervalle peut-il effectuer/vérifier des opérations/conditions supplémentaires?

Je découvre la boucle basée sur la gamme C++ 11 et je l’adore déjà. Cela vous fait gagner beaucoup de temps lors du codage.

Cependant, je suis habitué à écrire des boucles avec des instructions/conditions supplémentaires et je me demande si cela peut être réalisé en utilisant C++ 11 range based-loop:

1. Incrémentation supplémentaire

std::vector<int> v = { 1, 2, 3, 4, 5 };
size_t index = 0;
for ( std::vector<int>::const_iterator iter = v.begin(); iter != v.end(); ++iter, ++index )
{
    std::cout << "v at index " << index << " is " << *iter;
}

Pourrait devenir:

size_t index = 0;
for ( int val : v )
{
    std::cout << "v at index " << index << " is " << *iter;
    ++index;
}

Cependant, incrémenter index dans la boucle for est préférable car garanti (incrémenté même si la boucle for a par exemple des instructions continue)

Existe-t-il un moyen de déplacer ++index à l'intérieur de l'instruction for

2. Obtenir l'index d'itération de manière dynamique

std::vector<int> v = { 1, 2, 3, 4, 5 };
for ( std::vector<int>::const_iterator iter = v.begin(); iter != v.end(); ++iter )
{
    std::cout << "v at index " << ( iter - v.begin() ) << " is " << *iter;
}

Peut-on obtenir quelque chose de similaire avec une boucle basée sur une plage C++ 11? Existe-t-il un moyen de savoir combien d'itérations ont été effectuées jusqu'à présent?

3. Condition de sortie supplémentaire

J'utilise souvent cela dans le code où l'interruption est interdite en tant que directive de codage:

std::vector<int> v = { 1, 2, 3, 4, 5 };
bool continueLoop = true;
for ( std::vector<int>::const_iterator iter = v.begin(); iter != v.end() && continueLoop; ++iter )
{
    std::cout << "v value is " << *iter;
    if ( *iter == 4 )
        continueLoop = false;
}

Peut-on obtenir quelque chose de similaire avec une boucle basée sur la plage C++ 11 (pause sans exeuction)

32
jpo38

Malheureusement, vous ne pouvez pas placer l'incrément dans la plage en fonction de la boucle. Toutefois, dans votre cas particulier - puisque std::vector enregistre ses éléments en mémoire - vous pouvez simuler l’option 2 en utilisant des pointeurs (grâce à @ M.M et @ Jarod42 pour les corrections et améliorations): 

for ( const int& val : v )  {
    std::cout << "v at index " << &val-v.data() << " is " << val; 
}

plus générique:

for ( const auto& val : v )  {
    std::cout << "v at index " << std::addressof(val)-v.data() << " is " << val; 
}

L'autre chose que vous pouvez faire est d'écrire une classe index_range, qui représente une collection d'index sur laquelle vous pouvez effectuer une itération dans votre plage en fonction de la boucle for:

struct index_range_it {
    size_t idx;
    size_t operator*(){
        return idx;
    }
    index_range_it& operator++() {
        idx++;
        return (*this);
    }
};

bool operator!=(index_range_it l,index_range_it r) {
    return l.idx != r.idx;
}

struct index_range {
    size_t size;
    index_range_it end(){return index_range_it{size};}
    index_range_it begin(){return index_range_it{0};}
};

int main()
{
    for (auto i: index_range{v.size()}){
        std::cout << "v at index " << i << " is " << v[i]; 
    }        
}

Une implémentation complète de cette idée peut être trouvée, par exemple. ici

Une telle plage peut alors également être composée, l’itérateur renvoyant un objet proxy contenant l’index ainsi qu’une référence à l’objet actuel et la liaison structurée de c ++ 17 qui serait encore plus pratique à utiliser.

14
MikeMB

Jetez un coup d'œil à range-v3 et cppitertools .

cppitertools fournit un très pratique enumerate :

std::vector<int> v = { 1, 2, 3, 4, 5 };
for (auto&& e : enumerate(v))
{
    std::cout << "v at index " << e.index << " is " << e.element;
}

Range-v3 n'a malheureusement pas d'énumération, ce qui me rend très triste, mais vous pouvez composer votre propre en utilisant view::ints et view::Zip* . Range-v3 a le gros avantage d'être la base des gammes proposées pour la bibliothèque standard. La composition de la gamme permet de construire des abstractions propres.

En ce qui concerne votre dernier exemple, je dirais que vous devriez éviter complètement une boucle si vous devez réduire la complexité. Utilisez plutôt un algorithme approprié tel que std::find_if, std::any_of qui correspond à votre tâche sans que vous ayez à exprimer le flux de contrôle.

13
Zulan

Pour un conteneur général, vous ne pouvez pas obtenir l'index ni l'itérateur à partir d'une boucle basée sur une plage. Au lieu de cela, vous devez soit conserver une variable distincte, soit revenir à la boucle d'itérateur.

L'aspect itérateur peut être écrit un peu plus simplement depuis C++ 11:

for( auto iter = begin(v); iter != end(v); ++iter )

Pour le cas spécifique d'un vecteur, vous pouvez faire:

for ( auto& val : v )
{
    cout << "Index is " << (&val - &v[0]) << '\n';
}

ce qui fonctionne parce que les vecteurs utilisent un stockage contigu.

6
M.M

Voici un petit quelque chose qui peut faire # 2

#include <iterator>
#include <utility>
#include <type_traits>
#include <cstddef>

template<typename Range>
class RangeBasedAdaptor
{
    Range& range;
public:
    RangeBasedAdaptor(Range& r) : range(r) {}
    struct iterator;
    typedef typename std::remove_reference<decltype(*std::begin(range))>::type mapped_type;
    typedef decltype(std::begin(range)) underlying_iterator;

    struct value_type
    {
        std::size_t index() const { return idx; }
        mapped_type& value() { return *ui; }
        const mapped_type& value() const { return *ui; }
    private:
        std::size_t idx;
        underlying_iterator ui;
    friend
        struct iterator;
    };

    struct iterator
    {
        iterator();
        iterator& operator++() { ++val.ui; ++val.idx; return *this; }
        value_type& operator*() { return val; }
        bool operator!=(iterator other) { return val.ui != other.val.ui; }
    private:
        iterator( underlying_iterator ui, std::size_t idx ) { val.idx=idx; val.ui=ui; }
        value_type val;
    friend
        class RangeBasedAdaptor;
    };

    iterator begin() { return iterator{ std::begin(range), 0 }; }
    iterator end() { return iterator{ std::end(range), (std::size_t)-1 }; }
};

template<typename Range>
auto indexed(Range& r) -> RangeBasedAdaptor<Range>
{
    return {r};
}

// -------------------------------------------------------------------------------------

#include <iostream>
#include <vector>
#include <list>

int main()
{
    std::vector<int> foo = { 1,2,3,4,5,6 };

    for( auto& val : indexed(foo) )
    {
        val.value() += 3;
        std::cout << val.index() << " : " << val.value() << std::endl;
    }

    const std::list<float> foo2 = { 1.1f, 2.2f, 3.3f };

    for( auto& val : indexed(foo2) )
    {
        std::cout << val.index() << " : " << val.value() << std::endl;
    }
}

Il est uniquement destiné aux boucles basées sur la plage, d’où l’itérateur minimal.

4
sp2danny

Dans les langages informatiques, une boucle "pour" est traditionnellement une boucle avec des conditions de boucle spécifiées par le langage. Si le programmeur veut spécifier ses propres conditions de boucle, il utilise une boucle "while". De ce point de vue, les boucles for basées sur la plage de C++ sont la première fois que le langage possède réellement une construction de boucle "for". Il faut donc un peu de temps à un programmeur C++ pour comprendre que s’ils ne peuvent pas gérer les conditions de boucle générées par le compilateur, ils doivent utiliser une construction différente.

Cela dit, comme les itérateurs peuvent être des objets personnalisés, vous pouvez faire ce que vous voulez avec une boucle for basée sur une plage en écrivant vous-même un itérateur personnalisé. Dans le passé, je trouvais généralement que cet effort ne valait pas le code supplémentaire, à moins que vous n'utilisiez cet itérateur à plusieurs reprises.

1. Incrémentation supplémentaire

Toutefois, l’incrémentation de l’index dans la boucle for est préférable car garantis (incrémenté même si for boucle a des instructions continue pour exemple)

Y at-il un moyen de déplacer ++ index à l'intérieur de l'instruction for?

Oui, avec un itérateur personnalisé. Cependant, c'est beaucoup de travail. C'est facile:

for (auto element : container) {
   ++index;
}

Ici, nous savons aussi qu’il est garanti d’être incrémenté, parce qu’il est placé en haut avant toute déclaration de rupture ou de poursuite.

  1. Obtenir un index d'itération de manière dynamique

Peut-on obtenir quelque chose de similaire avec une boucle basée sur une plage C++ 11? Est y a-t-il moyen de savoir combien d'itérations ont été faites jusqu'à présent?

Encore une fois, cela pourrait être fait avec un itérateur personnalisé, mais presque certainement pas la peine. Je devais le faire moi-même la semaine dernière et la solution ressemblait beaucoup au code du n ° 1 ci-dessus.

  1. Condition de sortie supplémentaire

J'utilise souvent cela dans le code où la pause est interdite en tant que codage ligne directrice:

Ceci devrait jamais être dans une directive de codage. C'est complètement faux. Je ne dis pas que vous enfreignez vos directives. Mais je préconise que quiconque lit ceci ne mette jamais plus jamais cela dans un document de directives de codage.

Il existe une règle commune pour un bon codage structuré: tout bloc de code ne doit avoir qu'un seul point de sortie (alias: goto considéré comme dangereux ). Cependant, une boucle avec deux instructions de sortie n'a toujours qu'un seul point de sortie. Les deux sorties renvoient le contrôle au même point en dehors de la boucle. 

Plus concrètement, il existe de nombreux types de boucles qui doivent être bien plus compliquées (par exemple: plus difficiles à comprendre et à continuer de fonctionner correctement) si vous ne pouvez pas placer votre test de sortie au milieu de celles-ci. Si une directive vous oblige régulièrement à écrire du code plus obtus, c'est une mauvaise directive.

Encore une fois, vous pouvez contourner cela avec un itérateur personnalisé. Dans ce cas, je dirais que cela pourrait être la voie à suivre. Bien sûr, ses codes sont beaucoup plus codés que sa valeur juste pour contourner votre directive de codage stupide. Mais c'est la faute de la ligne directrice, pas la vôtre.

3
T.E.D.

Je n'écrirai pas de code qui remplace une instruction break parfaitement correcte.

Obtenir l'index d'un vecteur (ce qui est utile) est facile: itérateur sur auto& x:v puis soustraire std::addressof(x)-v.data().

Ce qui laisse # 1.

template<class It, class Operation>
struct iterator_with_extra_increment_t {
  using self=iterator_with_extra_increment_t;
  It it;
  Operation& op;
  void operator++(){ ++it; op(); }
  auto operator*()->decltype(*std::declval<It&>()) { return *it; }
  friend bool operator!=(self const& lhs, self const& rhs){
    return lhs.it != rhs.it;
  }
  friend bool operator==(self const& lhs, self const& rhs){
    return lhs.it == rhs.it;
  }
};
template<class It, class Operation>
iterator_with_extra_increment_t<It, Operation>
iterator_with_extra_increment( It it, Operation& operation ) {
  return {std::move(it), operation};
}
template<class Range, class Modify>
struct iterate_modified_t {
  Range r;
  Modify m;
  auto begin() { using std::begin; return m(begin(r)); }
  auto end() { using std::end; return m(end(r)); }
};
template<class Range, class Modify>
iterate_modified_t<Range, std::decay_t<Modify>>
iterate_modified( Range&& r, Modify&& m) {
  return {std::forward<Range>(r), std::forward<Modify>(m)};
}
template<class Range, class Op>
auto also_on_inc( Range&& r, Op&& op ) {
  auto modify = [op = std::forward<Op>(op)](auto&& it) {
    return iterator_with_extra_increment(decltype(it)(it), op);
  };
  return iterate_modified( std::forward<Range>(r), std::move(modify) );
}

maintenant nous avons also_on_inc:

std::vector<int> a = {1,2,3,4,5};
std::size_t count = 0;
for (int x : also_on_inc(a, [&]{++count;}) ) {
  std::cout << count << "->" << x << '\n';
}

exemple live .

Une partie du code ci-dessus est en C++ 14 car je suis trop paresseux pour écrire les clauses ->decltype.

Nous pouvons améliorer cette syntaxe avec l'abus d'opérateur à quelque chose comme:

std::vector<int> a = {1,2,3,4,5};
std::size_t count = 0;
for (int x : a *also_on_inc* [&]{++count;} ) {
  std::cout << count << "->" << x << '\n';
}    

si nous sommes fous, ce qui nous permet de faire

std::vector<int> a = {1,2,3,4,5};
std::size_t count = 0;
for (int x : a *also_on_inc* [&]{++count;} *also_on_inc* [&]{std::cout << count << '\n';} ) {
  std::cout << count << "->" << x << '\n';
}

chaînage plus facile de ces clauses.

0