loop instead for based c++ for-loop c++11 customization

c++ - instead - ¿Cómo hacer que mi tipo personalizado funcione con "rango para bucles"?



for auto p m c++ (7)

¿Debería especializarme en begin () y end ()?

Hasta donde yo sé, eso es suficiente. También debe asegurarse de que el aumento del puntero se realice desde el principio hasta el final.

El siguiente ejemplo (falta la versión de inicio y fin de const) compila y funciona bien.

#include <iostream> #include <algorithm> int i=0; struct A { A() { std::generate(&v[0], &v[10], [&i](){ return ++i;} ); } int * begin() { return &v[0]; } int * end() { return &v[10]; } int v[10]; }; int main() { A a; for( auto it : a ) { std::cout << it << std::endl; } }

Aquí hay otro ejemplo con begin / end como funciones. Deben estar en el mismo espacio de nombres que la clase, debido a ADL:

#include <iostream> #include <algorithm> namespace foo{ int i=0; struct A { A() { std::generate(&v[0], &v[10], [&i](){ return ++i;} ); } int v[10]; }; int *begin( A &v ) { return &v.v[0]; } int *end( A &v ) { return &v.v[10]; } } // namespace foo int main() { foo::A a; for( auto it : a ) { std::cout << it << std::endl; } }

Al igual que muchas personas en estos días, he estado probando las diferentes características que aporta C + 11. Uno de mis favoritos es el "rango para bucles".

Entiendo que:

for(Type& v : a) { ... }

Es equivalente a:

for(auto iv = begin(a); iv != end(a); ++iv) { Type& v = *iv; ... }

Y eso begin() simplemente devuelve a.begin() para contenedores estándar.

Pero, ¿y si quiero hacer que mi tipo personalizado "basado en rango para el ciclo" -aware ?

¿Debería especializarme en begin() y end() ?

Si mi tipo personalizado pertenece al espacio de nombres xml , ¿debería definir xml::begin() o std::begin() ?

En resumen, ¿cuáles son las pautas para hacer eso?


Aquí, comparto el ejemplo más simple de creación de tipo personalizado, que funcionará con " bucle for-based ":

#include<iostream> using namespace std; template<typename T, int sizeOfArray> class MyCustomType { private: T *data; int indx; public: MyCustomType(){ data = new T[sizeOfArray]; indx = -1; } ~MyCustomType(){ delete []data; } void addData(T newVal){ data[++indx] = newVal; } //write definition for begin() and end() //these two method will be used for "ranged based loop idiom" T* begin(){ return &data[0]; } T* end(){ return &data[sizeOfArray]; } }; int main() { MyCustomType<double, 2> numberList; numberList.addData(20.25); numberList.addData(50.12); for(auto val: numberList){ cout<<val<<endl; } return 0; }

Espero, será útil para algunos desarrolladores novatos como yo: p :)
Gracias.


El estándar ha sido cambiado desde que la pregunta (y la mayoría de las respuestas) se publicaron en la resolución de este informe de defectos .

La forma de hacer que un bucle for(:) funcione en su tipo X ahora es de dos maneras:

  • Crear miembros X::begin() y X::end() que devuelven algo que actúa como un iterador

  • Cree una función gratuita begin(X&) y end(X&) que devuelve algo que actúa como un iterador, en el mismo espacio de nombres que su tipo X.¹

Y similar para las variaciones de const . Esto funcionará tanto en los compiladores que implementan los cambios del informe de defectos como en los compiladores que no lo hacen.

Los objetos devueltos no tienen que ser realmente iteradores. El bucle for(:) , a diferencia de la mayoría de las partes del estándar C ++, se especifica para expandirse a algo equivalente a :

for( range_declaration : range_expression )

se convierte en:

{ auto && __range = range_expression ; for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) { range_declaration = *__begin; loop_statement } }

donde las variables que comienzan con __ son sólo para exposición, y begin_expr y end_expr es la magia que llama a begin / end

Los requisitos sobre el valor de retorno inicial / final son simples: debe sobrecargar pre ++ , asegurarse de que las expresiones de inicialización sean válidas, binarias != Que se puedan usar en un contexto booleano, unario * que devuelva algo que pueda asignar-inicializar range_declaration con, y exponer un destructor público.

Hacerlo de una manera que no es compatible con un iterador es probablemente una mala idea, ya que las iteraciones futuras de C ++ podrían ser relativamente despreocupadas sobre romper tu código si lo haces.

Por otro lado, es razonablemente probable que una revisión futura de la norma permita que end_expr devuelva un tipo diferente de begin_expr . Esto es útil ya que permite la evaluación de "extremo diferido" (como la detección de terminación nula) que es fácil de optimizar para ser tan eficiente como un ciclo C escrito a mano, y otras ventajas similares.

¹ Tenga en cuenta que for(:) bucles almacenan cualquier temporal en una variable auto&& , y se lo pasan como un lvalue. No puede detectar si está iterando sobre un temporal (u otro valor r); dicha sobrecarga no será llamada por un ciclo for(:) . Ver [stmt.ranged] 1.2-1.3 desde n4527.

² Llame el método de begin / end , o la búsqueda solo de ADL de la función gratuita de begin / end , o la magia para el soporte de matriz estilo C. Tenga en cuenta que std::begin no se llama a menos que range_expression devuelva un objeto de tipo en el namespace std de namespace std o dependa de él.

En c ++ 17 la expresión range-for ha sido actualizada

{ auto && __range = range_expression ; auto __begin = begin_expr; auto __end = end_expr for (;__begin != __end; ++__begin) { range_declaration = *__begin; loop_statement } }

con los tipos de __begin y __end han sido desacoplados.

Esto permite que el iterador final no sea del mismo tipo que begin. Su tipo de iterador final puede ser un "centinela" que solo admite != Con el tipo de iterador de inicio.

Un ejemplo práctico de por qué esto es útil es que su iterador final puede leer "verifique su char* para ver si apunta a ''0'' " cuando == con un char* . Esto permite que una expresión de rango C ++ genere código óptimo cuando itera sobre un búfer char* terminado en nulo.

struct null_sentinal_t { template<class Rhs, std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0 > friend bool operator==(Rhs const& ptr, null_sentinal_t) { return !*ptr; } template<class Rhs, std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0 > friend bool operator!=(Rhs const& ptr, null_sentinal_t) { return !(ptr==null_sentinal_t{}); } template<class Lhs, std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0 > friend bool operator==(null_sentinal_t, Lhs const& ptr) { return !*ptr; } template<class Lhs, std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0 > friend bool operator!=(null_sentinal_t, Lhs const& ptr) { return !(null_sentinal_t{}==ptr); } friend bool operator==(null_sentinal_t, null_sentinal_t) { return true; } friend bool operator!=(null_sentinal_t, null_sentinal_t) { return false; } };

ejemplo en vivo en un compilador sin soporte completo de C ++ 17; for bucle expandido manualmente.


En caso de que quiera respaldar la iteración de una clase directamente con su miembro std::vector o std::map , aquí está el código para eso:

#include <iostream> using std::cout; using std::endl; #include <string> using std::string; #include <vector> using std::vector; #include <map> using std::map; ///////////////////////////////////////////////////// /// classes ///////////////////////////////////////////////////// class VectorValues { private: vector<int> v = vector<int>(10); public: vector<int>::iterator begin(){ return v.begin(); } vector<int>::iterator end(){ return v.end(); } vector<int>::const_iterator begin() const { return v.begin(); } vector<int>::const_iterator end() const { return v.end(); } }; class MapValues { private: map<string,int> v; public: map<string,int>::iterator begin(){ return v.begin(); } map<string,int>::iterator end(){ return v.end(); } map<string,int>::const_iterator begin() const { return v.begin(); } map<string,int>::const_iterator end() const { return v.end(); } const int& operator[](string key) const { return v.at(key); } int& operator[](string key) { return v[key]; } }; ///////////////////////////////////////////////////// /// main ///////////////////////////////////////////////////// int main() { // VectorValues VectorValues items; int i = 0; for(int& item : items) { item = i; i++; } for(int& item : items) cout << item << " "; cout << endl << endl; // MapValues MapValues m; m["a"] = 1; m["b"] = 2; m["c"] = 3; for(auto pair: m) cout << pair.first << " " << pair.second << endl; }


Escribo mi respuesta porque algunas personas pueden estar más contentas con el ejemplo simple de la vida real sin incluir STL.

Por alguna razón tengo mi propia implementación de matriz de datos simple y quería usar el rango basado en bucle. Aquí está mi solución:

template <typename DataType> class PodArray { public: class iterator { public: iterator(DataType * ptr): ptr(ptr){} iterator operator++() { ++ptr; return *this; } bool operator!=(const iterator & other) { return ptr != other.ptr; } const DataType& operator*() const { return *ptr; } private: DataType* ptr; }; private: unsigned len; DataType *val; public: iterator begin() const { return iterator(val); } iterator end() const { return iterator(val + len); } // rest of the container definition not related to the question ... };

Luego el ejemplo de uso:

PodArray<char> array; // fill up array in some way for(auto& c : array) printf("char: %c/n", c);


La parte relevante del estándar es 6.5.4 / 1:

si _RangeT es un tipo de clase, los IDs no calificados comienzan y terminan en el ámbito de la clase _RangeT como si se tratara de una búsqueda de acceso de miembro de clase (3.4.5), y si alguno (o ambos) encuentra al menos una declaración, comience - expr y end-expr son __range.begin() y __range.end() , respectivamente;

- de lo contrario, begin-expr y end-expr son begin(__range) y end(__range) , respectivamente, donde begin y end se buscan con búsqueda dependiente de argumentos (3.4.2). A los efectos de esta búsqueda de nombres, namespace std es un espacio de nombres asociado.

Entonces, puedes hacer cualquiera de los siguientes:

  • definir las funciones de miembro de begin y end
  • defina las funciones gratuitas de begin y end que encontrará ADL (versión simplificada: colóquelas en el mismo espacio de nombres que la clase)
  • especializar std::begin y std::end

std::begin llama a la función de miembro de begin() todos modos, por lo que si solo implementa uno de los anteriores, los resultados deberían ser los mismos, independientemente del que elija. Ésos son los mismos resultados para bucles for ranged y también el mismo resultado para el mero código mortal que no tiene sus propias reglas de resolución de nombres mágicos, así que solo using std::begin; seguido de una llamada no calificada para begin(a) .

Sin embargo, si implementa las funciones miembro y las funciones ADL, entonces los bucles basados ​​en rango deben llamar a las funciones miembro, mientras que los simples mortales llamarán a las funciones ADL. ¡Mejor asegúrese de que hagan lo mismo en ese caso!

Si lo que está escribiendo implementa la interfaz del contenedor, ya tendrá las funciones de miembro begin() y end() , lo que debería ser suficiente. Si se trata de un rango que no es un contenedor (que sería una buena idea si es inmutable o si no conoce el tamaño por adelantado), puede elegir libremente.

De las opciones que diseñe, tenga en cuenta que no debe sobrecargar std::begin() . Se le permite especializar plantillas estándar para un tipo definido por el usuario, pero aparte de eso, agregar definiciones al espacio de nombres std es un comportamiento indefinido. Pero de todos modos, la especialización de las funciones estándar es una opción pobre si solo porque la falta de especialización de funciones parciales significa que solo se puede hacer por una sola clase, no por una plantilla de clase.


La respuesta de Chris Redford también funciona para contenedores Qt (por supuesto). Aquí hay una adaptación (note que devuelvo un constBegin() , respectivamente constEnd() de los métodos const_iterator):

class MyCustomClass{ QList<MyCustomDatatype> data_; public: // ctors,dtor, methods here... QList<MyCustomDatatype>::iterator begin() { return data_.begin(); } QList<MyCustomDatatype>::iterator end() { return data_.end(); } QList<MyCustomDatatype>::const_iterator begin() const{ return data_.constBegin(); } QList<MyCustomDatatype>::const_iterator end() const{ return data_.constEnd(); } };