Estratégias de Ligação de Argumentos com std::bind em C++

Se uma lambda não possui lista de captura (escopo vazio), ela geralmente pode ser substituída diretamente por uma função convencional. Contudo, situações envolvem captura de variáveis locais onde essa troca direta encontra obstáculos. Considere um cenário onde desejamos filtrar elementos baseado em um critério dinâmico, como o tamanho mínimo de uma string, utilizando algoritmos que exigem predicações unárias.

Nós poderíamos escrever uma função auxiliar que recebe tanto o texto quanto o valor de limite:

bool verificaTamanho(const std::string &palavra, size_t limite) {
    return palavra.length() >= limite;
}

No entanto, passá-la diretamente para algo como std::find_if é inviável. Tais algoritmos esperam um objeto chamável que aceite apenas um parâmetro (a string atual da iteração), enquanto nossa função exige dois. A lambda resolve isso capturando a variável limite, mas queremos atingir o mesmo resultado usando uma função clássica sem capturar estado.

O Adaptador std::bind

A biblioteca padrão oferece a função bind, disponível no cabeçalho <functional>, para resolver esse problema de aridade. Ela atua como um adaptador genérico: recebe uma entidade chamável e gera outra nova, ajustada para o conjunto de argumentos necessário.

A sintaxe básica envolve a criação de um novo objeto invocável:

auto novoChamavel = bind(entidade_chamavel, lista_de_args);

O resultado, novoChamavel, será chamado posteriormente, enviando os valores presentes na lista_de_args para a entidade_chamavel. Dentro dessa lista, podemos utilizar marcadores conhecidos como placeholders, representados pela sintaxe _n (onde n é um inteiro). Eles indicam quais posições dos argumentos do novo objeto corresponderão aos parâmetros da função original.

Fixando Valores de Parâmetro

Para ilustrar, suponha que precisamos de uma função que verifique se um texto tem pelo menos 10 caracteres, mas mantenha a interface de aceitar apenas o texto. Podemos usar bind assim:

// filtroTexto espera apenas um string e compara internamente com 10
auto filtroTexto = bind(verificaTamanho, _1, 10);

Aqui, há apenas um placeholder (_1), indicando que o objeto resultante aceita um único argumento. Quando invocado, esse argumento vai ocupar a primeira posição de verificaTamanho.

std::string exemplo = "Olá mundo";
bool resultado = filtroTexto(exemplo); // Chama verificaTamanho("Olá mundo", 10)

Dessa forma, podemos substituir uma lambda que captura tamanhoAlvo dentro de um algoritmo de busca por uma instância vinculada:

auto inicioValido = std::find_if(
    colecao.begin(), 
    colecao.end(), 
    bind(verificaTamanho, _1, tamanhoAlvo)
);

O adaptador garante que, durante a iteração, cada elemento seja enviado como primeiro argumento, enquanto tamanhoAlvo permanece fixo como segundo parâmetro.

Manipulação e Ordem de Argumentos

Além de fixar valores, o mecanismo permite reorganizar a ordem de entrada dos dados. Imagine um objeto processamento que necessita de cinco entradas. Podemos criar um wrapper que aceite apenas duas:

// wrapper aceita 2 args, mapeia _2 para o 3º param e _1 para o 5º
auto wrapper = bind(processamento, const1, const2, _2, const3, _1);

Nesta configuração, os parâmetros 1, 2 e 4 são constantes definidas no momento da ligação. O primeiro argumento passado ao wrapper alimentará a quinta posição de processamento, e o segundo argumento alimentará a terceira. Assim, uma chamada como wrapper(A, B) executa efetivamente processamento(const1, const2, B, const3, A).

Vínculo com Referências

É crucial notar que, por padrão, os argumentos passados para bind (que não sejam placeholders) são copiados. Isso funciona bem para tipos fundamentais ou strings, mas falha com objetos que possuem regras estritas de não-copiabilidade, como fluxos de entrada/saída.

Considere a necessidade de substituir uma lambda que captura um fluxo de saída por referência:

std::for_each(lista.begin(), lista.end(), [&fluxo](const std::string &texto){
    fluxo << texto;
});

Uma função equivalente seria definida como:

void escreve(std::ostream &destino, const std::string &conteudo) {
    destino << conteudo;
}

Tentar vincular o fluxo diretamente resulta em erro, pois bind tentaria copiar o objeto std::ostream:

// Erro: std::ostream não é copiável
// std::for_each(..., bind(escreve, meuFluxo, _1)); 

A solução reside no auxílio da função std::ref. Ela retorna um objeto especial que encapsula a referência e é copiável, permitindo que o adaptador gerencie o vínculo corretamente:

std::for_each(lista.begin(), lista.end(), 
    bind(escreve, std::ref(meuFluxo), _1)
);

Existe também a variante std::cref, útil para criar referências constantes dentro desse contexto. Todas essas ferramentas estão hospedadas no cabeçalho <functional> da biblioteca padrão.

Tags: cpp STL std-bind c-plus-plus functors

Publicado em 9-6 22:31