Pregunta ¿Puede C ++ 11 basado en rango para hacer / verificar operaciones / condiciones adicionales?


Estoy descubriendo el loop basado en C ++ 11 y ya me encanta. Le hace ahorrar mucho tiempo cuando codifica.

Sin embargo, estoy acostumbrado a escribir algunos bucles con sentencias / condiciones adicionales y me pregunto si esto se puede lograr al usar el bucle basado en el rango C ++ 11:

1. Incremento adicional

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;
}

Podría convertirse:

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

Sin embargo, incrementando index en el for loop es mejor porque está garantizado (se incrementa incluso si for lazo tiene continue declaraciones, por ejemplo)

¿Hay alguna manera de moverse? ++index dentro de for ¿declaración?

2. Obtenga el índice de iteración dinámicamente

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;
}

¿Se puede lograr algo similar con el ciclo basado en el rango de C ++ 11? ¿Hay alguna forma de saber cuántas iteraciones se realizaron hasta ahora?

3. condición de salida extra

A menudo uso esto en el código donde el break está prohibido como una guía de codificación:

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;
}

¿Se puede lograr algo similar con el ciclo basado en el rango de C ++ 11 (la excepción de ruptura sin usar un descanso)?


32
2018-04-14 07:04


origen


Respuestas:


Desafortunadamente, no puedes poner el incremento en el rango basado en el ciclo. Sin embargo, en su caso específico - como std::vector almacena sus elementos contundentemente en la memoria: puede simular la opción 2 volviendo a los punteros (gracias a @M y @Jodod42 para correcciones y mejoras):

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

más genérico:

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

La otra cosa que puedes hacer es escribir un index_range clase, que representa una colección de índices sobre los que puede iterar en su rango basado en bucle:

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]; 
    }        
}

Se puede encontrar una implementación completa de esta idea, p. aquí

Tal rango también puede estar compuesto por algo, donde el iterador devuelve un objeto proxy que contiene el índice, así como una referencia al objeto actual y con el enlace estructurado de c ++ 17 que sería aún más conveniente de usar.


14
2018-04-14 07:16



Echa un vistazo a range-v3 y cppitertools.

cppitertools proporciona una muy conveniente 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 lamentablemente no tiene enumerate, lo que me entristece mucho, pero puede componer el suyo usando view::ints y view::zip*. Range-v3 tiene la gran ventaja de que es la base para los rangos propuestos en la biblioteca estándar. La composición de rango permite construir abstracciones limpias.

En cuanto a su último ejemplo, yo diría que debe evitar un ciclo completo si necesita reducir la complejidad. En su lugar, use un algoritmo apropiado como std::find_if, std::any_of que coincida con su tarea sin tener que expresar el flujo de control.


13
2018-04-14 10:41



Para un contenedor general, no puede obtener el índice ni el iterador de un bucle de rango. En su lugar, debe mantener una variable separada o volver al ciclo del iterador.

El aspecto del iterador se puede escribir de forma un poco más simple desde C ++ 11:

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

Para el caso específico de un vector, puede hacer:

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

que funciona porque los vectores usan almacenamiento contiguo.


6
2018-04-14 07:26



Aquí hay algo que puede hacer # 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;
    }
}

Solo está diseñado con bucles basados ​​en rangos, de ahí el iterador mínimo.


4
2018-04-14 09:56



En los lenguajes de computadora, tradicionalmente un bucle "for" es un bucle con condiciones de bucle especificadas por el lenguaje. Si el programador desea especificar sus propias condiciones de bucle, utilizan un ciclo "while". Desde esta perspectiva, los bucles for basados ​​en rangos de C ++ son la primera vez que el lenguaje realmente tiene una verdadera construcción de bucle "para". Por lo tanto, a un programador de C ++ le puede llevar un poco pensar en el hecho de que si no pueden lidiar con las condiciones de bucle generadas por el compilador, deberían usar una construcción diferente.

Dicho esto, dado que los iteradores pueden ser objetos personalizados, puede hacer lo que desee con un bucle for basado en rango escribiéndose un iterador personalizado. En el pasado, normalmente he encontrado que este esfuerzo no vale la pena el código adicional, a menos que vayas a reutilizar ese iterador varias veces.

1. Incremento adicional

Sin embargo, incrementar el índice en el ciclo for es mejor porque   garantizado (incrementado incluso si el ciclo tiene declaraciones continuas para   ejemplo)

¿Hay alguna manera de mover el índice ++ dentro de la instrucción for?

Sí, con un iterador personalizado. Sin embargo, eso es mucho trabajo. Esto es facil:

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

Aquí también sabemos que está garantizado para aumentar, porque se coloca en la parte superior antes de cualquier posible ruptura o continuar las declaraciones.

  1. Obtenga el índice de iteración dinámicamente

¿Se puede lograr algo similar con el ciclo basado en el rango de C ++ 11? Es   Hay una manera de saber cuántas iteraciones se realizaron hasta ahora?

Una vez más, esto podría hacerse con un iterador personalizado, pero casi seguro que no vale la pena. Tuve que hacer esto la semana pasada, y la solución se parecía mucho al código en el n. ° 1 anterior.

  1. Condición de salida extra

A menudo uso esto en código donde el break está prohibido como codificación   guidline:

Esto debería Nunca estar en una guía de codificación. Está completamente equivocado. No estoy argumentando para que rompa sus pautas. Pero estoy argumentando que cualquiera que lea esto nunca volverá a poner tal cosa en un documento de directrices de codificación.

Existe una regla empírica común para una buena codificación estructurada de que cualquier bloque de código solo debe tener un punto de salida (también conocido como: Goto considerado perjudicial) Sin embargo, un bucle con dos instrucciones de salida todavía tiene un solo punto de salida. Ambas salidas devuelven el control al mismo punto fuera del bucle.

Más prácticamente, hay muchos tipos de bucles que tienen que ser mucho más complicados (por ej., Más difíciles de entender y seguir funcionando correctamente) si no puede poner su prueba de salida en el medio de ellos. Si una directriz te obliga rutinariamente a escribir más código obtuso, es una mala guía.

Nuevamente, podría solucionar esto con un iterador personalizado. En este caso, yo diría que puede ser el camino a seguir. Claro, es mucho más código de lo que vale solo para evitar su estúpida guía de codificación. Pero esa es la falla de la guía, no la tuya.


3
2018-04-14 15:16



No escribiré código que reemplace a un perfectamente bueno break declaración.

Obtener el índice de un vector (que es cuando es útil) es fácil: iterador sobre auto& x:vy luego restar std::addressof(x)-v.data().

Que deja # 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) );
}

ahora tenemos 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';
}

ejemplo en vivo.

Parte del código anterior es C ++ 14 porque soy demasiado perezoso para escribir el ->decltype cláusulas.

Podemos mejorar esa sintaxis con el abuso del operador a algo como:

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 estamos locos, lo que nos permite hacer

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';
}

encadenamiento más fácil de tales cláusulas.


0
2018-04-14 14:02