¿Alguna razón para no usar lambdas globales?

89

Teníamos una función que usaba una lambda interna que no capturaba, por ejemplo:

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

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

Ahora la funcionalidad implementada por el lambda se hizo necesaria en otros lugares, por lo que voy a sacar el lambda del foo()alcance global / espacio de nombres. Puedo dejarlo como lambda, convirtiéndolo en una opción de copiar y pegar, o cambiarlo a una función adecuada:

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
}

Cambiarlo a una función adecuada es trivial, pero me hizo preguntarme si hay alguna razón para no dejarlo como una lambda. ¿Hay alguna razón para no usar lambdas en todas partes en lugar de funciones globales "regulares"?

Baruch
fuente
Supongo que capturar variables no deseadas conduciría a muchos errores si las lambdas no se usan con cuidado
macroland
algunos argumentos para esto youtube.com/watch?v=Ft3zFk7VPrE
sudo rm -rf slash

Respuestas:

61

Hay una razón muy importante para no usar lambdas globales: porque no es normal.

La sintaxis de la función regular de C ++ ha existido desde los días de C. Los programadores han sabido durante décadas qué significa dicha sintaxis y cómo funcionan (aunque es cierto que la descomposición de la función de puntero a veces muerde incluso a programadores experimentados). Si un programador de C ++ de cualquier nivel más allá del "novato absoluto" ve una definición de función, sabe lo que está obteniendo.

Una lambda global es una bestia completamente diferente. Tiene un comportamiento diferente al de una función regular. Las lambdas son objetos, mientras que las funciones no lo son. Tienen un tipo, pero ese tipo es distinto del tipo de su función. Etcétera.

Así que ahora, has elevado el listón en la comunicación con otros programadores. Un programador de C ++ necesita comprender lambdas si va a comprender qué está haciendo esta función. Y sí, esto es 2019, por lo que un programador decente de C ++ debería tener una idea de cómo es una lambda. Pero sigue siendo una barra más alta.

E incluso si lo entienden, la pregunta en la mente de ese programador será ... ¿por qué el escritor de este código lo escribió de esa manera? Y si no tiene una buena respuesta para esa pregunta (por ejemplo, porque quiere prohibir explícitamente la sobrecarga, como en los puntos de personalización de Rangos), entonces debe usar el mecanismo común.

Prefiere las soluciones esperadas a las nuevas cuando sea apropiado. Use el método menos complicado para transmitir su punto de vista.

Nicol Bolas
fuente
99
"desde los días de C" Mucho antes de eso, mi amigo
ligereza corre en órbita el
44
@LightnessRaces: mi punto principal fue expresar que la sintaxis de la función de C ++ ha existido desde C, por lo que mucha gente sabe lo que es mediante inspección.
Nicol Bolas
2
De acuerdo con toda esta respuesta, excepto la primera oración. "No es normal" es una declaración vaga y nebulosa. ¿Quizás lo dijiste en el sentido de "es poco común y confuso"?
einpoklum
1
@einpoklum: C ++ tiene muchas cosas que podrían considerarse "poco comunes y confusas" que todavía son bastante "normales" dentro de su dominio (ver metaprogramación de plantilla profunda). Creo que "normal" es perfectamente válido en este caso; no es algo que esté dentro de las "normas" de la experiencia de programación en C ++, tanto históricamente como hoy en día.
Nicol Bolas
@NicolBolas: Pero en ese sentido, es un razonamiento circular: OP se pregunta si deberíamos adoptar una práctica poco común. Las prácticas raras no son "la norma", casi por definición. Pero nm.
einpoklum
53

Puedo pensar en algunas razones por las que desearía evitar las lambdas globales como reemplazos directos para las funciones regulares:

  • las funciones regulares se pueden sobrecargar; lambdas no puede (sin embargo, existen técnicas para simular esto)
  • A pesar del hecho de que son similares a funciones, incluso una lambda que no captura puede ocupar memoria (generalmente 1 byte para no capturar).
    • Como se señaló en los comentarios, los compiladores modernos optimizarán este almacenamiento bajo la regla de si

"¿Por qué no debería usar lambdas para reemplazar a los functores con estado (clases)?"

  • las clases simplemente tienen menos restricciones que las lambdas y, por lo tanto, deberían ser lo primero que alcances
    • (datos públicos / privados, sobrecarga, métodos auxiliares, etc.)
  • si la lambda tiene estado, entonces es más difícil razonar cuándo se vuelve global.
    • Deberíamos preferir crear una instancia de una clase en el alcance más estrecho posible
  • Ya es difícil convertir una lambda que no captura en un puntero de función, y es imposible para una lambda que especifique algo en su captura.
    • las clases nos brindan una forma sencilla de crear punteros de función, y también son con lo que muchos programadores se sienten más cómodos
  • Las lambdas con cualquier captura no pueden construirse por defecto (en C ++ 20. Anteriormente no había ningún constructor por defecto en ningún caso)
AndyG
fuente
También está el puntero implícito "this" a un lambda (incluso uno que no captura) que da como resultado un parámetro adicional al llamar al lambda frente a llamar a la función.
1201ProgramAlarm
@ 1201ProgramAlarm: Ese es un buen punto; puede ser mucho más difícil obtener un puntero de función para una lambda (aunque las lambdas que no capturan pueden descomponerse en punteros de función regulares)
AndyG
3
Por otro lado, si se pasa a algún lugar en lugar de llamarse directamente, tener una lambda en lugar de una función promueve la inserción. Naturalmente, uno podría empaquetar el puntero de función en un std::integral_constantpara eso ...
Deduplicator
@Dupuplicator: " tener una lambda en lugar de una función promueve la alineación " Solo para ser claros, esto solo se aplica si el "algún lugar" es una plantilla que toma la función que llama como un tipo invocable arbitrario, en lugar de un puntero de función.
Nicol Bolas
2
@AndyG Te estás olvidando de la regla como si: los programas que no solicitan el tamaño de la lambda (porque ... ¿por qué?), Ni crean un puntero a ella, no es necesario (y generalmente no lo hacen) asignar espacio para ello.
Konrad Rudolph
9

Después de preguntar, pensé en una razón para no hacer esto: dado que estas son variables, son propensas al Fiasco de orden de inicialización estática ( https://isocpp.org/wiki/faq/ctors#static-init-order ), que podría causar errores en la línea.

Baruch
fuente
Usted podría hacer que constexprlambdas ... el punto real es: Sólo tiene que utilizar una función.
einpoklum
8

¿Hay alguna razón para no usar lambdas en todas partes en lugar de funciones globales "regulares"?

Un problema de cierto nivel de complejidad requiere una solución de al menos la misma complejidad. Pero si hay una solución menos compleja para el mismo problema, entonces realmente no hay justificación para usar la más compleja. ¿Por qué introducir la complejidad que no necesitas?

Entre una lambda y una función, una función es simplemente el tipo menos complejo de entidad de las dos. No tiene que justificar no usar una lambda. Tienes que justificar el uso de uno. Una expresión lambda introduce un tipo de cierre, que es un tipo de clase sin nombre con todas las funciones de miembros especiales habituales, un operador de llamada de función y, en este caso, un operador de conversión implícito a puntero de función, y crea un objeto de ese tipo. Copiar-inicializar una variable global de una expresión lambda simplemente hace mucho más que solo definir una función. Define un tipo de clase con seis funciones declaradas implícitamente, define dos funciones de operador más y crea un objeto. El compilador tiene que hacer mucho más. Si no necesita ninguna de las características de una lambda, entonces no use una lambda ...

Michael Kenzel
fuente
6

Las lambdas son funciones anónimas .

Si está utilizando una lambda con nombre, significa que básicamente está utilizando una función anónima con nombre. Para evitar este oxímoron, también podrías usar una función.

Eric Duminil
fuente
1
Esto parece ser un argumento de la terminología, es decir. nombre y significado confusos. Se puede refutar fácilmente definiendo que una lambda con nombre ya no es anónima (duh). El problema aquí no es cómo se usa la lambda, es que la "función anónima" es un nombre inapropiado y ya no es precisa cuando una lambda está vinculada a un nombre.
Konrad Rudolph
@KonradRudolph: No se trata solo de terminología, por eso se crearon en primer lugar. Al menos históricamente, las lambdas son funciones anónimas, y por eso es confuso nombrarlas, especialmente cuando se esperarían funciones en su lugar.
Eric Duminil
1
Nah Las lambdas se nombran rutinariamente (por lo general, localmente), no hay nada confuso al respecto.
Konrad Rudolph
1
No estoy de acuerdo con las funciones anónimas , es más un functor, pero de todos modos, no veo el problema de tener una instancia (nombrada) en lugar de obligar a usarlas solo como referencia de valor. (ya que su punto también debe aplicarse en el ámbito local).
Jarod42
5

si hay alguna razón para no dejarlo como una lambda? ¿Hay alguna razón para no usar lambdas en todas partes en lugar de funciones globales "regulares"?

Solíamos usar funciones en lugar de functor global, por lo que rompe la coherencia y el Principio de menor asombro .

Las principales diferencias son:

  • las funciones se pueden sobrecargar, mientras que los functores no.
  • Las funciones se pueden encontrar con ADL, no con functors.
Jarod42
fuente