Alguma razão para não usar lambdas globais?

89

Tínhamos uma função que usava um lambda não captador interno para si mesmo, por exemplo:

void foo() {
  auto bar = [](int a, int b){ return a + b; }

  // code using bar(x,y) a bunch of times
}

Agora, a funcionalidade implementada pelo lambda tornou-se necessária em outro lugar, por isso vou elevar o lambda para foo()o escopo global / namespace. Posso deixá-lo como um lambda, tornando-o uma opção de copiar e colar ou alterá-lo para uma função adequada:

auto bar = [](int a, int b){ return a + b; } // option 1
int bar(int a, int b){ return a + b; } // option 2

void foo() {
  // code using bar(x,y) a bunch of times
}

Mudá-lo para uma função adequada é trivial, mas me fez pensar se há algum motivo para não deixá-lo como um lambda? Existe alguma razão para não usar lambdas em todos os lugares, em vez de funções globais "regulares"?

Baruch
fonte
Eu assumo a captura variáveis inesperadas levaria a muitos erros se lambdas não são cuidadosamente usado
macroland
alguns argumentos para este youtube.com/watch?v=Ft3zFk7VPrE
sudo rm -rf slash

Respostas:

61

Há uma razão muito importante para não usar lambdas globais: porque não é normal.

A sintaxe da função regular do C ++ existe desde os dias de C. Os programadores sabem há décadas o que essa sintaxe significa e como eles funcionam (embora seja certo que toda a deterioração de função para ponteiro às vezes morde até programadores experientes). Se um programador de C ++ de qualquer nível de habilidade além de "novato absoluto" vir uma definição de função, ele saberá o que está recebendo.

Um lambda global é um animal completamente diferente. Tem um comportamento diferente de uma função regular. Lambdas são objetos, enquanto funções não. Eles têm um tipo, mas esse tipo é distinto do tipo de sua função. E assim por diante.

Então, agora, você elevou a fasquia na comunicação com outros programadores. Um programador de C ++ precisa entender lambdas se quiser entender o que esta função está fazendo. E sim, isso é 2019, então um programador de C ++ decente deve ter uma idéia de como é um lambda. Mas ainda é uma barra mais alta.

E mesmo que eles entendam, a pergunta na mente do programador será ... por que o escritor desse código o escreveu dessa maneira? E se você não tiver uma boa resposta para essa pergunta (por exemplo, porque deseja proibir explicitamente a sobrecarga, como nos pontos de personalização do Ranges), use o mecanismo comum.

Prefira as soluções esperadas às novas, quando apropriado. Use o método menos complicado de transmitir sua opinião.

Nicol Bolas
fonte
9
"desde os dias de C" Muito antes disso, meu amigo
Lightness Races in Orbit
4
@LightnessRaces: Meu ponto principal foi expressar que a sintaxe da função do C ++ existe desde C, então muitas pessoas sabem o que é isso por inspeção.
Nicol Bolas
2
Concorde com toda essa resposta, exceto a primeira frase. "Não é normal" é uma afirmação vaga e nebulosa. Talvez você quis dizer isso no sentido de "é incomum e confuso"?
einpoklum
11
@einpoklum: C ++ tem muitas coisas que podem ser consideradas "incomuns e confusas" que ainda são bastante "normais" em seu domínio (consulte metaprogramação profunda de modelos). Eu acho que "normal" é perfeitamente válido neste caso; não é algo que esteja dentro das "normas" da experiência em programação em C ++, tanto historicamente quanto hoje.
Nicol Bolas
@ NicolBolas: Mas, nesse sentido, é um raciocínio circular: a OP está se perguntando se devemos adotar uma prática rara. Práticas raras não são "a norma", quase por definição. Mas nm.
einpoklum
53

Posso pensar em alguns motivos pelos quais você desejaria evitar lambdas globais como substitutos de entrada para funções regulares:

  • funções regulares podem ser sobrecarregadas; lambdas não podem (no entanto, existem técnicas para simular isso)
  • Apesar de serem funcionais, até mesmo um lambda não capturador como esse ocupará memória (geralmente 1 byte para não capturar).
    • conforme indicado nos comentários, os compiladores modernos otimizarão esse armazenamento de acordo com a regra do tipo " como se "

"Por que eu não deveria usar lambdas para substituir functores com estado (classes)?"

  • As classes simplesmente têm menos restrições que as lambdas e, portanto, devem ser a primeira coisa a ser alcançada.
    • (dados públicos / privados, sobrecarga, métodos auxiliares etc.)
  • se o lambda tem estado, é ainda mais difícil argumentar quando se torna global.
    • Devemos preferir criar uma instância de uma classe no escopo mais estreito possível
  • já é difícil converter um lambda que não captura em um ponteiro de função e é impossível para um lambda que especifique qualquer coisa em sua captura.
    • As classes nos fornecem uma maneira direta de criar ponteiros de função, e também são com isso que muitos programadores se sentem mais confortáveis.
  • Lambdas com qualquer captura não podem ser construídas por padrão (no C ++ 20. Antes, não havia construtor padrão em nenhum caso)
AndyG
fonte
Há também o ponteiro implícito "this" para um lambda (mesmo que não seja de captura) que resulta em um parâmetro extra ao chamar o lambda vs a função.
1201ProgramAlarm
@ 1201ProgramAlarm: Esse é um bom ponto; é pode ser muito mais difícil obter um ponteiro de função para um lambda (embora lambdas não-captura pode decair em ponteiros de função regulares)
AndyG
3
Por outro lado, se for passado para algum lugar em vez de chamado diretamente, ter um lambda em vez de uma função promove inlining. Naturalmente, pode-se embalar a função de ponteiro em uma std::integral_constantpara isso ...
Deduplicator
@ Reduplicator: " ter um lambda em vez de uma função promove inlining " Só para deixar claro, isso só se aplica se o "algures" for um modelo que aceita a função que chama como um tipo de chamada arbitrária, em vez de um ponteiro de função.
Nicol Bolas
2
@AndyG Você está esquecendo a regra como se: programas que não solicitam o tamanho da lambda (porque ... por que ?!), nem criam um ponteiro para ela, não precisam (e geralmente não) alocar espaço para isso.
Konrad Rudolph
9

Depois de perguntar, pensei em um motivo para não fazer isso: como são variáveis, elas são propensas ao Fiasco da ordem de inicialização estática ( https://isocpp.org/wiki/faq/ctors#static-init-order ), que poderia causar erros na linha.

Baruch
fonte
Você poderia fazê-los constexprlambdas ... o ponto real é: basta usar uma função.
einpoklum
8

Existe alguma razão para não usar lambdas em todos os lugares, em vez de funções globais "regulares"?

Um problema de um certo nível de complexidade requer uma solução de pelo menos a mesma complexidade. Mas se houver uma solução menos complexa para o mesmo problema, não há realmente justificativa para usar a mais complexa. Por que introduzir complexidade que você não precisa?

Entre um lambda e uma função, uma função é simplesmente o tipo menos complexo de entidade dos dois. Você não precisa justificar não usar um lambda. Você tem que justificar usando um. Uma expressão lambda apresenta um tipo de fechamento, que é um tipo de classe sem nome com todas as funções-membro especiais usuais, um operador de chamada de função e, nesse caso, um operador de conversão implícito no ponteiro de função e cria um objeto desse tipo. A inicialização de cópia de uma variável global a partir de uma expressão lambda simplesmente faz muito mais do que apenas definir uma função. Ele define um tipo de classe com seis funções declaradas implicitamente, define mais duas funções de operador e cria um objeto. O compilador precisa fazer muito mais. Se você não precisar de nenhum dos recursos de um lambda, não use um lambda…

Michael Kenzel
fonte
6

Lambdas são funções anônimas .

Se você estiver usando um lambda nomeado, significa que você está basicamente usando uma função anônima nomeada. Para evitar esse oxímoro, você também pode usar uma função.

Eric Duminil
fonte
11
Este parece ser um argumento da terminologia, ie. nomes e significados confusos. Pode ser refutado facilmente, definindo que um lambda nomeado não é mais anônimo (duh). O problema aqui não é como o lambda é usado, é que "função anônima" é um nome impróprio e não é mais preciso quando um lambda é vinculado a um nome.
Konrad Rudolph
@KonradRudolph: Não é apenas terminologia, é por isso que eles foram criados em primeiro lugar. Pelo menos historicamente, as lambdas são funções anônimas, e é por isso que é confuso nomeá-las, especialmente quando as funções seriam esperadas.
Eric Duminil
11
Nah. Os lambdas são rotineiramente nomeados (geralmente geralmente localmente), não há nada de confuso nisso.
Konrad Rudolph
11
Discordo das funções anônimas , é mais um functor, mas, de qualquer forma, não vejo o problema de ter uma instância (nomeada) em vez de forçar a usá-las apenas como referência de valor-r. (como seu argumento também deve ser aplicado no escopo local).
Jarod42
5

se houver algum motivo para não deixá-lo como um lambda? Existe alguma razão para não usar lambdas em todos os lugares, em vez de funções globais "regulares"?

Costumávamos usar funções em vez de functor global, por isso quebra a coerência e o Princípio de menor espanto .

As principais diferenças são:

  • funções podem ser sobrecarregadas, enquanto functors não podem.
  • funções podem ser encontradas com ADL, não functors.
Jarod42
fonte