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 tipo X
es ahora una de dos maneras:
Crear miembro X::begin()
y X::end()
que devuelva algo que actúa como un iterador
Cree una función libre begin(X&)
y end(X&)
que devuelva algo que actúe como un iterador, en el mismo espacio de nombres que su tipo X
.
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 :
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 solo para exposición, y begin_expr
y end_expr
es la magia que llama begin
/ 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-inicializar range_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 de begin_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 una auto&&
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 un for(:)
bucle. Ver [stmt.ranged] 1.2-1.3 de n4527.
² Llame al método begin
/ end
, o busque solo ADL de función libre begin
/ end
, o magia para soporte de matriz de estilo C. Tenga en cuenta que std::begin
no se llama a menos que range_expression
devuelva un objeto de tipo namespace std
o dependiente del mismo.
En c ++ 17 la expresión de rango para 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 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 un char*
. Esto permite que una expresión de rango de C ++ genere un código óptimo al iterar sobre un char*
búfer con terminación nula .
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.
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/…