Como muchas personas en estos días, he estado probando las diferentes características que trae C ++ 11. Uno de mis favoritos es el "basado en 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 regresa a.begin()
para contenedores estándar.
Pero, ¿qué sucede si quiero que mi tipo personalizado sea "basado en rango para bucle" ?
¿Debo especializarme begin()
y end()
?
Si mi tipo personalizado pertenece al espacio de nombres xml
, ¿debo definir xml::begin()
o std::begin()
?
En resumen, ¿cuáles son las pautas para hacer eso?
c++
for-loop
c++11
customization
ereOn
fuente
fuente
begin/end
o un amigo, estático o librebegin/end
. Solo tenga cuidado en qué espacio de nombres coloca la función gratuita: stackoverflow.com/questions/28242073/…for( auto x : range<float>(0,TWO_PI, 0.1F) ) { ... }
. Tengo curiosidad por cómo evitar el hecho de que `´operator! = ()` `Es difícil de definir. ¿Y qué hay de la desreferenciación (*__begin
) en este caso? ¡Creo que sería una gran contribución si alguien nos mostrara cómo se hace eso !Respuestas:
El estándar ha 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
for(:)
bucle funcione en su tipoX
es ahora una de dos maneras:Crear miembro
X::begin()
yX::end()
que devuelva algo que actúa como un iteradorCree una función libre
begin(X&)
yend(X&)
que devuelva algo que actúe como un iterador, en el mismo espacio de nombres que su tipoX
.Y similar para las
const
variaciones. 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 iteradores. El
for(:)
bucle, a diferencia de la mayoría de las partes del estándar C ++, se especifica para expandirse a algo equivalente a :se convierte en:
donde las variables que comienzan con
__
son solo para exposición, ybegin_expr
yend_expr
es la magia que llamabegin
/end
.²Los requisitos sobre el valor de retorno de inicio / fin son simples: debe sobrecargar previamente
++
, asegurarse de que las expresiones de inicialización sean válidas, binarias!=
que se pueden usar en un contexto booleano, unario*
que devuelve algo con lo que puede asignar-inicializarrange_declaration
y exponer un público incinerador de basuras.Hacerlo de una manera que no sea compatible con un iterador es probablemente una mala idea, ya que las futuras iteraciones de C ++ podrían ser relativamente cautelosas sobre romper su código si lo hace.
Por otro lado, es razonablemente probable que una futura revisión de la norma permita
end_expr
devolver un tipo diferente debegin_expr
. Esto es útil porque permite una evaluación de "final diferido" (como la detección de terminación nula) que es fácil de optimizar para ser tan eficiente como un bucle C escrito a mano, y otras ventajas similares.¹ Tenga en cuenta que los
for(:)
bucles almacenan cualquier temporal en unaauto&&
variable y se lo pasan a usted como un lvalue. No puede detectar si está iterando sobre un valor temporal (u otro valor r); tal sobrecarga no será invocada por unfor(:)
bucle. Ver [stmt.ranged] 1.2-1.3 de n4527.² Llame al método
begin
/end
, o busque solo ADL de función librebegin
/end
, o magia para soporte de matriz de estilo C. Tenga en cuenta questd::begin
no se llama a menos querange_expression
devuelva un objeto de tiponamespace std
o dependiente del mismo.En c ++ 17 la expresión de rango para ha sido actualizada
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 es compatible
!=
con el tipo de iterador de inicio.Un ejemplo práctico de por qué esto es útil es que su iterador final puede leer "compruebe
char*
si está apuntando'0'
" cuando==
con unchar*
. Esto permite que una expresión de rango de C ++ genere un código óptimo al iterar sobre unchar*
búfer con terminación nula .ejemplo en vivo en un compilador sin soporte completo de C ++ 17;
for
bucle expandido manualmente.fuente
begin
yend
funciones diferentes de las que están disponibles en el código normal. Quizás podrían ser muy especializados para comportarse de manera diferente (es decir, más rápido al ignorar el argumento final para obtener las máximas optimizaciones posibles). Pero no soy lo suficientemente bueno con los espacios de nombres para estar seguro de cómo hacerlo.begin(X&&)
. El temporal se suspende en el aireauto&&
en un rango basado en, ybegin
siempre se llama con un valor l (__range
).Escribo mi respuesta porque algunas personas podrían estar más felices con un ejemplo simple de la vida real sin STL incluido.
Tengo mi propia implementación de matriz de datos simple por alguna razón, y quería usar el rango basado en bucle. Aquí está mi solución:
Entonces el ejemplo de uso:
fuente
const
calificador de devoluciónconst DataType& operator*()
y permitir que el usuario elija usarconst auto&
oauto&
? Gracias de todos modos, gran respuesta;)La parte relevante de la norma es 6.5.4 / 1:
Entonces, puede hacer lo siguiente:
begin
yend
funciones miembrobegin
yend
libres que ADL encontrará (versión simplificada: colóquelas en el mismo espacio de nombres que la clase)std::begin
ystd::end
std::begin
llama a labegin()
función miembro de todos modos, por lo que si solo implementa uno de los anteriores, los resultados deberían ser los mismos sin importar cuál elija. Esos son los mismos resultados para los bucles basados en rangos, y también el mismo resultado para el simple código mortal que no tiene sus propias reglas mágicas de resolución de nombres, por lo que solo lousing std::begin;
sigue una llamada no calificadabegin(a)
.Sin embargo, si implementa las funciones miembro y las funciones ADL, los bucles basados en rangos deberían llamar a las funciones miembro, mientras que los simples mortales llamarán a las funciones ADL. ¡Mejor asegúrate de que hagan lo mismo en ese caso!
Si lo que estás escribiendo implementa la interfaz contenedor, entonces tendrá
begin()
yend()
funciones miembro, ya que debería ser suficiente. Si se trata de un rango que no es un contenedor (lo 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 sobrecargarse
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 estándar es un comportamiento indefinido. Pero de todos modos, la especialización de las funciones estándar es una mala elección, solo porque la falta de especialización parcial de la función significa que solo puede hacerlo para una sola clase, no para una plantilla de clase.fuente
!=
, prefijo++
y unario*
. Probablemente no sea prudente implementarbegin()
yend()
funciones miembro o funciones ADL no miembros que devuelven cualquier cosa que no sea un iterador, pero creo que es legal. Especializadostd::begin
en devolver un no iterador es UB, creo.Que yo sepa, eso es suficiente. También debe asegurarse de que el incremento del puntero se obtenga desde el principio hasta el final.
El siguiente ejemplo (falta la versión constante de inicio y fin) se compila y funciona bien.
Aquí hay otro ejemplo con begin / end como funciones. Ellos tienen que estar en el mismo espacio de nombres que la clase, debido a ADL:
fuente
return v + 10
.&v[10]
desreferencia la ubicación de la memoria justo después de la matriz.En caso de que desee realizar una copia de iteración de una clase directamente con su
std::vector
ostd::map
miembro, aquí está el código para ello:fuente
const_iterator
también se puede acceder en unauto
(C ++ 11) forma compatible a través decbegin
,cend
, etc.Aquí, comparto el ejemplo más simple de crear tipos personalizados, que funcionará con " bucle basado en rango ":
Espero que sea útil para algunos desarrolladores novatos como yo: p :)
Gracias.
fuente
end()
función en sí misma obviamente no elimina la referencia de una ubicación de memoria inadecuada, ya que solo toma la 'dirección de' esta ubicación de memoria. Agregar un elemento adicional significaría que necesitaría más memoria, y usarloyour_iterator::end()
de cualquier manera que desreferenciara ese valor no funcionaría con ningún otro iterador de todos modos porque están construidos de la misma manera.return &data[sizeofarray]
mi humilde opinión, solo debería devolver los datos de la dirección + sizeofarray, pero qué sé,data + sizeofarray
sería la forma correcta de escribir esto.La respuesta de Chris Redford también funciona para contenedores Qt (por supuesto). Aquí hay una adaptación (observe que devuelvo a
constBegin()
, respectivamente,constEnd()
de los métodos const_iterator):fuente
Me gustaría elaborar algunas partes de la respuesta de @Steve Jessop, que al principio no entendí. Espero eso ayude.
https://en.cppreference.com/w/cpp/language/range-for :
Para el bucle basado en rango, las funciones miembro se seleccionan primero.
Pero para
Las funciones ADL se seleccionan primero.
Ejemplo:
fuente