Como muitas pessoas hoje em dia, tenho tentado os diferentes recursos que o C ++ 11 traz. Um dos meus favoritos é o "intervalo para loops".
Eu entendi aquilo:
for(Type& v : a) { ... }
É equivalente a:
for(auto iv = begin(a); iv != end(a); ++iv)
{
Type& v = *iv;
...
}
E isso begin()
simplesmente retorna a.begin()
para contêineres padrão.
Mas e se eu quiser tornar meu tipo personalizado "baseado em intervalo para loop" ?
Devo apenas me especializar begin()
e end()
?
Se meu tipo personalizado pertencer ao espaço para nome xml
, devo definir xml::begin()
ou std::begin()
?
Em resumo, quais são as diretrizes para fazer isso?
c++
for-loop
c++11
customization
ereOn
fonte
fonte
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/...for( auto x : range<float>(0,TWO_PI, 0.1F) ) { ... }
. Estou curioso para saber como você trabalha com o fato de que `´operator! = ()` `É difícil de definir. E o desreferenciamento (*__begin
) neste caso? Eu acho que seria uma grande contribuição se alguém nos mostrasse como isso é feito!Respostas:
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 tipoX
agora é uma das duas maneiras:Criar membro
X::begin()
eX::end()
retornar algo que age como um iteradorCrie uma função livre
begin(X&)
eend(X&)
que retorne algo que age como um iterador, no mesmo espaço de nome que seu tipoX
.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 :torna-se:
onde as variáveis que começam com
__
são apenas para exposição ebegin_expr
eend_expr
é a mágica que chamabegin
/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-inicializarrange_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 diferentebegin_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 umaauto&&
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 umfor(:)
loop. Veja [stmt.ranged] 1.2-1.3 da n4527.² Ou chamar o
begin
/end
método, ou ADL-única pesquisa de função livrebegin
/end
, ou mágica para suporte de matriz de estilo C. Observe questd::begin
não é chamado, a menos querange_expression
retorne um objeto do tiponamespace std
ou dependente do mesmo.No c ++ 17 a expressão de intervalo para foi atualizada
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 achar*
. Isso permite que uma expressão de intervalo de C ++ gere código ideal ao iterar em umchar*
buffer terminado por nulo .exemplo ao vivo em um compilador sem suporte completo a C ++ 17;
for
loop expandido manualmente.fonte
begin
eend
funções diferentes do que está disponível no código normal. Talvez eles possam ser muito especializados para se comportar de maneira diferente (ou seja, mais rápido ignorando o argumento final para obter as otimizações possíveis ao máximo). Mas eu não sou bom o suficiente com espaços para nome para ter certeza de como fazer isso.begin(X&&)
. O temporário é suspenso no ar porauto&&
um intervalo baseado em ebegin
é sempre chamado com um lvalue (__range
).Escrevo minha resposta porque algumas pessoas podem ficar mais felizes com um exemplo simples da vida real sem o STL.
Eu tenho minha própria implementação simples de matriz de dados por algum motivo e queria usar o intervalo baseado em loop. Aqui está a minha solução:
Em seguida, o exemplo de uso:
fonte
const
qualificador de retornoconst DataType& operator*()
e deixar o usuário optar por usarconst auto&
ouauto&
? Obrigado de qualquer maneira, ótima resposta;)A parte relevante da norma é 6.5.4 / 1:
Portanto, você pode fazer o seguinte:
begin
eend
funções-membrobegin
eend
liberar funções que serão encontradas pelo ADL (versão simplificada: coloque-as no mesmo espaço de nome da classe)std::begin
estd::end
std::begin
chama abegin()
função membro de qualquer maneira, portanto, se você implementar apenas uma das opções acima, os resultados deverão ser os mesmos, independentemente de qual você escolher. Esses são os mesmos resultados para loops baseados em varredura, e também o mesmo resultado para mero código mortal que não possui suas próprias regras de resolução de nomes mágicos, o que éusing std::begin;
seguido por uma chamada não qualificada parabegin(a)
.Se você implementar as funções de membro e as funções de ADL, no entanto, os loops baseados em intervalo devem chamar as funções de membro, enquanto meros mortais chamarão as funções de ADL. Melhor garantir que eles façam a mesma coisa nesse caso!
Se a coisa que você está escrevendo implementa a interface recipiente, então ele terá
begin()
eend()
funções membro já, que deve ser suficiente. Se for um intervalo que não é um contêiner (o que seria uma boa ideia se for imutável ou se você não souber o tamanho antecipadamente), você poderá escolher.Das opções apresentadas, observe que você não deve sobrecarregar
std::begin()
. Você tem permissão para especializar modelos padrão para um tipo definido pelo usuário, mas, além disso, adicionar definições ao namespace std é um comportamento indefinido. Mas, enfim, a especialização de funções padrão é uma má escolha, apenas porque a falta de especialização parcial da função significa que você pode fazê-lo apenas para uma única classe, não para um modelo de classe.fonte
!=
, prefixo++
e unário*
. É provavelmente imprudente para implementarbegin()
eend()
funções membro ou funções ADL terceiros que o retorno outra coisa senão um iterador, mas eu acho que é legal. Especializarstd::begin
para devolver um não-iterador é o UB, eu acho.Até onde eu sei, isso é suficiente. Você também precisa garantir que o incremento do ponteiro seja obtido do começo ao fim.
O próximo exemplo (está faltando a versão const do begin e end) compila e funciona bem.
Aqui está outro exemplo com begin / end como funções. Eles precisam estar no mesmo espaço para nome da classe, devido à ADL:
fonte
return v + 10
.&v[10]
desreferencia a localização da memória logo após a matriz.Caso você queira fazer o backup da iteração de uma classe diretamente com o membro
std::vector
oustd::map
, aqui está o código para isso:fonte
const_iterator
também pode ser acessado em umauto
(++ 11 C) forma-compatível viacbegin
,cend
etc.Aqui, estou compartilhando o exemplo mais simples de criação de tipo personalizado, que funcionará com " loop for for baseado em intervalo ":
Espero que seja útil para algum desenvolvedor iniciante como eu: p :)
Obrigado.
fonte
end()
função em si, obviamente, não faz dereference um local de memória imprópria, já que ele só tem o 'endereço-de' este local de memória. Adicionar um elemento extra significaria que você precisaria de mais memória e usaryour_iterator::end()
de qualquer maneira que desdiferenciasse esse valor não funcionaria com outros iteradores, porque eles são criados da mesma maneira.return &data[sizeofarray]
IMHO ele deve apenas retornar o endereço de dados + sizeofarray mas o que eu sei,data + sizeofarray
seria a maneira correta de escrever isso.A resposta de Chris Redford também funciona para contêineres Qt (é claro). Aqui está uma adaptação (observe que eu retorno a
constBegin()
, respectivamente,constEnd()
dos métodos const_iterator):fonte
Gostaria de elaborar algumas partes da resposta de Steve Jessop, para as quais, a princípio, não entendi. Espero que ajude.
https://en.cppreference.com/w/cpp/language/range-for :
Para loop for baseado em intervalo, as funções de membro são selecionadas primeiro.
Mas pelo
As funções ADL são selecionadas primeiro.
Exemplo:
fonte