Pegue isso
#include <range/v3/view/remove_if.hpp>
#include <range/v3/range/conversion.hpp>
#include <vector>
std::vector<int> foo(std::vector<int> v, bool(*p)(int)) {
return v | ranges::views::remove_if(p) | ranges::to_vector;
}
em comparação com isso
#include <range/v3/action/remove_if.hpp>
#include <vector>
std::vector<int> bar(std::vector<int> v, bool(*p)(int)) {
return std::move(v) | ranges::actions::remove_if(p);
}
Não há modelos disponíveis, apenas duas TUs fornecendo, cada uma, uma função pura com a mesma assinatura. Dada a sua implementação, esperaria que as duas funções realizassem a mesma tarefa, do ponto de vista do chamador. E é isso que eles parecem fazer.
No entanto, eles compilam em códigos bastante diferentes, a tal ponto que o GCC (tronco, pelo menos) produz código mais curto para o último, enquanto o Clang (tronco) produz código mais curto para o primeiro.
Não vejo nenhuma razão para as duas funções compilarem em códigos diferentes, a não ser "é muito difícil para o compilador criar o mesmo código para ambos" , mas o que está tornando isso tão difícil? Ou, se estiver errado, por que as duas funções devem ser compiladas em assembly diferente?
E, além do benchmarking, existe uma razão pela qual eu deveria preferir uma em vez de outra implementação?
Não tenho certeza se é teoricamente possível que os dois gerem o mesmo código. Vamos examinar as duas abordagens.
Ações
Com ações, isso é tomar
v
, alterá-lo para remover os elementos que satisfazemp
e retornar o mesmov
. Isso equivale a ter escrito:Ou, antes do C++20:
Definitivamente , não há alocação, estamos apenas movendo um monte de
int
s e depois mudandov.size()
.Visualizações
views::remove_if
é um filtro preguiçoso. Isso nos dá uma visão sobre os elementosv
que não satisfazemp
. Então,to_vector
vai construir um newvector
, que requer alocação, e copiar todos os elementosv
que não satisfazemp
em um newvector
. Esse novo vetor é retornado.Será que a
misturaserá otimizada?Inicialmente, a expressão
v | remove_if(p) | to_vector
aloca um newvector<int>
distinto dev
.v
está ativo durante todo o comprimento desta expressão, portanto você não pode reutilizarv
a memória de aqui.A otimização aqui não seria apenas reconhecer que
v
está sendo destruído iminentemente e assim sua alocação pode ser reutilizada. Mas também que o novovector
tenha no máximo o mesmo tamanho, porv
isso reutilizar a sua alocação é uma estratégia viável. Mas também que os elementos desta novavector
sejam preenchidos de uma forma que permita reutilizar essa alocação.Fundamentalmente, os dois casos são apenas algoritmos diferentes. Às vezes, os compiladores conseguem descobrir isso, mas isso parece um grande exagero. Se tal otimização existisse, ela seria basicamente feita à mão para este cenário.
Em geral, a resposta a esta pergunta será utilizar a ferramenta mais específica para o trabalho. Se você tem a
vector<int>
e deseja apenas os elementos que não satisfazemp
e não precisa dos elementos originais - isso éactions::remove_if
(ou, dependendo do contexto, apenas uma chamada direta parastd::erase_if
). Esse é o trabalho queactions::remove_if
foi criado para resolver.Se você não precisa de um contêiner com todos os elementos que satisfaçam
p
e só precisa escolher (alguns) deles sob demanda - isso éviews::remove_if
.Às vezes, ansioso é melhor. Às vezes, a preguiça é melhor. Realmente depende do problema.