std :: back_inserter para um std :: set?

94

Eu acho que esta é uma pergunta simples. Eu preciso fazer algo assim:

std::set<int> s1, s2;
s1 = getAnExcitingSet();
std::transform(s1.begin(), s1.end(), std::back_inserter(s2), ExcitingUnaryFunctor());

Claro, std::back_inserternão funciona, pois não há push_back. std::insertertambém precisa de um iterador? Eu não usei, std::inserterentão não tenho certeza do que fazer.

Alguém tem alguma ideia?


Claro, minha outra opção é usar um vetor para s2e apenas classificá-lo mais tarde. Talvez seja melhor?

rlbond
fonte

Respostas:

139

setnão tem push_backporque a posição de um elemento é determinada pelo comparador do conjunto. Use std::insertere passe .begin():

std::set<int> s1, s2;
s1 = getAnExcitingSet();
transform(s1.begin(), s1.end(), 
          std::inserter(s2, s2.begin()), ExcitingUnaryFunctor());

O iterador insert irá então chamar s2.insert(s2.begin(), x)where xis o valor passado para o iterador quando escrito nele. O conjunto usa o iterador como uma dica de onde inserir. Você poderia tão-bem usar s2.end().

Johannes Schaub - litb
fonte
2
Já que inserter(vec, vec.end())funciona para vetores também, por que alguém usa back_inserter em primeiro lugar?
NHDaly
7
@NHDaly: porque back_inserter é mais rápido
marton78
@ marton78 Mas não deveria ser apenas mais rápido por uma margem muito pequena, se tanto? Chamar em insertvez de push_backem um vetor deve ser aproximadamente idêntico (O (1)) quando nenhum elemento precisa ser movido.
Felix Dombek
3
@FelixDombek você tem razão, não será muito mais lento. v.insert(x, v.end())terá uma ramificação adicional no início (devido a mover n elementos, mas aqui n é zero). No entanto, usar inserter1) comunica uma intenção diferente de usar push_back2) é incomum e faz o leitor parar e pensar 3) é uma pessimização prematura.
marton78
0

Em 2016 houve uma proposta para ter um " inserteriterador de argumento único ". https://isocpp.org/files/papers/p0471r0.html . Não consegui descobrir se a proposta avançou. Eu acho que faz sentido.

Por enquanto, você pode ter este comportamento definindo a função do fabricante:

template<class Container>
auto sinserter(Container& c){
    using std::end;
    return std::inserter(c, end(c));
}

Usado como:

std::transform(begin(my_vec), end(my_vec), sinserter(my_set), [](auto& e){return e.member;});
alfC
fonte
Isso é planejado para funcionar em todos os contêineres padrão? Ele não funciona em std :: forward_list (o erro do compilador é "forward_list não tem nenhum membro chamado 'insert'", dentro da instanciação de insert_iterator::operator=). Deveria?
Don Hatch,
@DonHatch, qualquer coisa que tenha insert(e end). parece que forward_listnão tem uma insertoperação em primeiro lugar, apenas insert_after. E mesmo que seja alterado, não pode ser inserido após o final, eu acho. Você não pode usar um std::list?
AlfC
Claro, eu pessoalmente não preciso de std :: forward_list. Mas estou interessado em um genérico "como copio um contêiner para outro?" para todos os pares de recipientes para os quais faz sentido. Meu interesse atual é exercitar alocadores de contêineres genéricos, como short_alloc de @HowardHinnant.
Don Hatch
@DonHatch, o fato é que existem muitas variantes para essa pergunta. ("" como faço para copiar um contêiner para outro? "). Por exemplo, você deseja preservar os valores originais, deseja minimizar as alocações, etc. Para o caso mais simples, a melhor resposta em minha opinião é usar o construtor o contêiner que leva dois iteradores (a maioria dos contêineres pode fazer isso) NewContaner new_container(old_other_container.begin(), old_other_container.end()).
alfC
1
Sim, std :: set não é um SequentialContainer, e tudo bem :-) existing_list = std::list(c.begin(), c.end(), existing_list.get_allocator()) Muito bom, acho que essa é a minha resposta. Felicidades!
Don Hatch