O padrão foi alterado desde que a pergunta (e a maioria das respostas) foi publicada na resolução deste relatório de defeitos .
A maneira de fazer um for(:)
loop funcionar no seu tipo X
agora é uma das duas maneiras:
Criar membro X::begin()
e X::end()
retornar algo que age como um iterador
Crie uma função livre begin(X&)
e end(X&)
que retorne algo que age como um iterador, no mesmo espaço de nome que seu tipo X
.
E semelhante para const
variações. Isso funcionará tanto nos compiladores que implementam as alterações no relatório de defeitos quanto nos compiladores que não o fazem.
Os objetos retornados não precisam ser realmente iteradores. O for(:)
loop, diferente da maioria das partes do padrão C ++, é especificado para expandir para algo equivalente a :
for( range_declaration : range_expression )
torna-se:
{
auto && __range = range_expression ;
for (auto __begin = begin_expr,
__end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
onde as variáveis que começam com __
são apenas para exposição e begin_expr
e end_expr
é a mágica que chama begin
/ end
.²
Os requisitos do valor de retorno inicial / final são simples: você deve sobrecarregar pré- ++
, garantir que as expressões de inicialização sejam válidas, binárias !=
que podem ser usadas em um contexto booleano, unárias *
que retornam algo com o qual você pode atribuir-inicializar range_declaration
e expor um público destruidor.
Fazer isso de uma maneira que não seja compatível com um iterador é provavelmente uma péssima idéia, já que futuras iterações do C ++ podem ser relativamente descuidadas quanto à quebra de seu código, se você o fizer.
Como um aparte, é razoavelmente provável que uma revisão futura da norma permita end_expr
retornar um tipo diferente begin_expr
. Isso é útil, pois permite a avaliação de "final lento" (como a detecção de terminação nula) que é fácil de otimizar para ser tão eficiente quanto um loop C escrito à mão e outras vantagens semelhantes.
¹ Observe que os for(:)
loops armazenam qualquer temporário em uma auto&&
variável e o passam para você como um valor l. Você não pode detectar se está iterando sobre um valor temporário (ou outro rvalue); essa sobrecarga não será chamada por um for(:)
loop. Veja [stmt.ranged] 1.2-1.3 da n4527.
² Ou chamar o begin
/ end
método, ou ADL-única pesquisa de função livre begin
/ end
, ou mágica para suporte de matriz de estilo C. Observe que std::begin
não é chamado, a menos que range_expression
retorne um objeto do tipo namespace std
ou dependente do mesmo.
No c ++ 17 a expressão de intervalo para foi atualizada
{
auto && __range = range_expression ;
auto __begin = begin_expr;
auto __end = end_expr;
for (;__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
com os tipos de __begin
e __end
foram dissociados.
Isso permite que o iterador final não seja do mesmo tipo que o inicial. O seu tipo de iterador final pode ser um "sentinela" que suporta apenas !=
o tipo de iterador de início.
Um exemplo prático de por que isso é útil é que o iterador final pode ler "verifique seu char*
para ver se aponta para '0'
" quando estiver ==
com a char*
. Isso permite que uma expressão de intervalo de C ++ gere código ideal ao iterar em um char*
buffer terminado por 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;
}
};
exemplo ao vivo em um compilador sem suporte completo a C ++ 17; for
loop expandido manualmente.
begin/end
ou um amigo, estático ou gratuitobegin/end
. Basta ter cuidado em que namespace você colocar a função livre: stackoverflow.com/questions/28242073/...