Como mover com eficiência (alguns) itens de um std :: map para outro?

8

Eu tenho dois std::map<>objetos ae bgostaria de mover ( extract+ insert) alguns elementos (nós) de um mapa para outro com base em algum predicado p.

for (auto i = a.begin(); i != a.end(); ++i)
    if (p(*i))
        b.insert(a.extract(i))

Esse código é segmentado em clang. Suponho que o problema é o incremento de idepois que seu nó foi extraído de um.

É a maneira correta / única de corrigir isso usando um pós-incremento ?, Por exemplo:

for (auto i = a.begin(); i != a.end();)
    if (p(*i))
        b.insert(a.extract(i++))
    else
        ++i;

Edição : Eu removi a parte sobre "por que isso funciona no gcc?", Porque não consigo reproduzir isso na minha configuração atual. Estou convencido de que isso costumava acontecer em algum momento, mas com o gcc 9.2.1 eu recebo um impasse (em vez de um segfault). De qualquer forma, incrementar depois extract()não está funcionando.

axxel
fonte
2
Relacionado a, ou duplicado de: stackoverflow.com/questions/6438086/iterator-invalidation-rules
Eljay
3
@Eljay Na minha opinião, a nova API de splicing de mapeamento de nós no C ++ 17 é suficientemente especializada para justificar sua própria pergunta. Espero que isso não esteja fechado como duplicado.
NicholasM
Possível duplicata de Excluindo elementos de std :: set durante a iteração . std::sete std::mapsão muito semelhantes, e até onde eu sei, extracttem as mesmas implicações de invalidação que erase.
François Andrieux
Qual versão do clang e do gcc você usou? Para mim, usando o clang 8.0 e o gcc 7.4, ambos resultam em um segfault.
Balázs Kovacsics 13/11/19
Estou surpreso que esse código funcione em qualquer compilador. Você não está lidando com a invalidação causando pelo extrato
Iman Kianrostami

Respostas:

6

Suponho que o problema é o incremento de i depois que o nó foi extraído de a.

De fato. A extração invalida os iteradores para o elemento extraído e ié esse iterador. O comportamento de incrementar ou indiretamente por meio de um iterador inválido é indefinido.

Por que isso aparentemente funciona no gcc, mas não no clang?

Porque o comportamento do programa é indefinido.

A maneira correta / única de corrigir isso com um pós-incremento?

É uma maneira correta de corrigir isso. Não é um caminho particularmente ruim. Se você preferir não repetir o incremento, uma abordagem é usar uma variável:

for (auto i = a.begin(); i != a.end();) {
    auto current = i++;
    if (p(*current)) {
        // moving is probably unnecessary
        b.insert(a.extract(std::move(current)));
    }
}
eerorika
fonte
É uma boa maneira, (razoavelmente), assumindo que copiar o estado do iterador é menos caro do que copiar o nó.
Spencer #
@ Spencer copiar um iterador é geralmente trivial. Mas eu adicionei uma jogada por precaução.
eerorika
O @Spencer currentseria deixado em um estado movido. Qualquer que seja esse estado, não importará, pois não será mais usado depois disso.
eerorika
@eeroika Obrigado, olhei seu código um pouco mais de perto e percebi isso.
Spencer #
Eu gosto mais da sua variável local do que ter dois icrements, mas eu sugeriria uma pequena melhoria: limite o escopo currentusando a if (auto current = ++i; p(*current))sintaxe c ++ 17s .
axxel 13/11/19