functional expresiones examples cppreference cplusplus c++ c++11 lambda default-arguments

expresiones - ¿Qué sucede si la expresión lambda de C++ 11 admite argumentos predeterminados?



std c++ (3)

Creo que el siguiente código es muy útil y no dañino:

auto fn = [](bool b = false) -> int // NOT legal in C++11 { return b ? 1 : 0; };

¿Por qué C ++ 11 prohíbe explícitamente los argumentos predeterminados de la expresión lambda?

Sólo me pregunto los fundamentos y consideraciones detrás.

Quiero saber "POR QUÉ" en lugar de "QUÉ" dice el estándar C ++ 11.


Como lo sugieren los comentarios a la pregunta, probablemente no haya una razón técnica para que no haya argumentos predeterminados. Pero la pregunta de la tapa es "¿Existe una razón práctica para los argumentos predeterminados?" Pensaría que la respuesta a esto es "no", y he aquí por qué.

Para llamar a un lambda, una cosa que puedes hacer es llamarlo de inmediato.

[] { printf("foo"); }();

Estoy seguro de que esto tiene usos limitados, si los hay, así que sigamos adelante. La única otra manera de llamar a un lambda es vincularlo a una variable primero, y hay algunas opciones aquí.

  1. Utilizar auto Entonces obtenemos auto foo = [] { printf("foo"); }; foo(); auto foo = [] { printf("foo"); }; foo();
  2. Enlazar a un puntero de función: void (*foo)() = [] { printf("foo"); }; foo() void (*foo)() = [] { printf("foo"); }; foo() void (*foo)() = [] { printf("foo"); }; foo() . Por supuesto, esto solo funciona si la lambda no captura.
  3. Haga el equivalente de 1 o 2 pasando la lambda a una función o plantilla de función.

Ahora vamos a ver la utilidad de los argumentos predeterminados en cada caso.

  1. Estamos llamando a la lambda directamente en este caso, por lo que el código probablemente sea lo suficientemente unido como para no llamar a la lambda con varios argumentos. Si lo somos, la lambda podría (debería?) Probablemente ser refactora en un componente más general. No veo ningún beneficio práctico aquí.
  2. (ver 1)
  3. Pasamos la lambda a otra función. Tampoco veo el beneficio práctico de un argumento predeterminado aquí. Piense en los viejos funtores buenos (que pueden tener argumentos predeterminados). No puedo decir que conozco demasiadas funciones que cuentan con la presencia de argumentos predeterminados incluso para estos. Dado que las lambdas son en realidad solo funtores, no hay razón para que esta observación cambie repentinamente.

Creo que estos puntos son suficientes para decir que los argumentos predeterminados a un lambda realmente no son tan útiles. Además, veo que algunas personas están hablando de problemas con el tipo de un lambda si tenían argumentos predeterminados, pero esto no es un problema de la OMI. Siempre puede escribir su propio functor que haga lo mismo y que tenga el argumento predeterminado. Además, acerca de degradar a un puntero de función, tampoco hay mucho que decir. Tomar una función normal

void func(int i = 0) { }

y toma su direccion. ¿Qué sacas? Un void (*)(int) . No hay razón para que una lambda siga reglas diferentes.


Estoy de acuerdo en que no existe una restricción "técnica" real per se para permitir que los argumentos predeterminados en lambdas funcionen en algunos casos . No estropearía tus punteros y tu auto , porque el tipo de una función no se ve afectada por los argumentos predeterminados. Pero también es por eso que esto no sería terriblemente práctico.

¿Por qué?

Debido a que los argumentos predeterminados, aunque forman parte de la firma de una función, no forman parte de un tipo de función:

[C++11: 1.3.17]:
firma
<función> nombre, lista de tipos de parámetros (8.3.5) y espacio de nombres adjunto (si corresponde)
[ Nota: las firmas se utilizan como base para la manipulación y el enlace de nombres. "Nota final "

[C++11: 8.3.5/6]: [..] El tipo de retorno, la lista de tipos de parámetros , el calificador ref y el calificador cv-calificador , pero no los argumentos predeterminados (8.3.6 ) o la especificación de excepción (15.4), son parte del tipo de función. [ Nota: los tipos de función se verifican durante las asignaciones e inicializaciones de punteros a funciones, referencias a funciones y punteros a funciones miembros. "Nota final "

Son esencialmente una pieza de azúcar sintáctica que se "activa" cuando el compilador puede ver la declaración de la función que usa, y se inyecta en el punto de la llamada de función:

#include <iostream> void foo(int x = 5) { std::cout << x << ''/n''; } int main() { foo(); }

  • Salida: 5

El argumento por defecto es "visible".

Sin embargo, cuando "ocultas" tu función detrás de un puntero:

int main() { void (*bar)(int) = &foo; bar(); }

  • Error: too few arguments to function

El tipo de bar es correcto, y el compilador sabe que foo tiene un valor predeterminado, pero simplemente no existe una sintaxis directa para informar al compilador en el punto de llamada que la bar también es foo . Claro, en este escenario trivial podría resolverlo observando la tarea, pero eso no es justificación para el argumento más amplio.

Por el mismo motivo, los argumentos predeterminados indicados solo en una definición no visible en el sitio de la llamada son casi inútiles:

// a.h void foo(int); // a.cpp #include "a.h" #include <iostream> void foo(int x = 5) { std::cout << x << ''/n''; } // main.cpp #include "a.h" int main() { foo(); }

  • Error en main.cpp: too few arguments to function

Me imagino que esta es la razón para:

[C++11: 8.3.6/4]: [..] Las declaraciones en diferentes ámbitos tienen conjuntos completamente distintos de argumentos predeterminados. [..]

Se nos permite "acumular" argumentos predeterminados para definiciones de funciones de miembros de la clase que no son plantillas ( [C++11 8.3.6/6] ); el ejemplo indica que este valor predeterminado todavía se aplicará solo en la misma TU, que sigue el comportamiento que hemos visto anteriormente en mi segundo fragmento de código.

Por lo tanto, si los argumentos predeterminados no son parte del tipo de función, y deben ser visibles de forma inequívoca para el sitio de la llamada, entonces solo hay un puñado de casos esquineros creados de forma trivial en los que serían útiles para las lambdas, que es cuando se llaman en el mismo ámbito en el que se crearon para que el compilador pueda determinar de manera viable cómo "completar" el argumento predeterminado para la llamada a la lambda y, bueno, ¿cuál es el punto de eso? No mucho, te lo digo.


No hay ninguna razón real por la que las lambdas no puedan tener argumentos predeterminados. Sin embargo, hay dos formas principales de usar lambdas, y solo una de ellas permitiría los argumentos predeterminados sin cambiar el sistema de tipos.

  1. Puede llamar a la lambda directamente, oa través de una plantilla. Los parámetros por defecto funcionarían bien en este caso.

  2. Puede llamar a la lambda a través de la std::function . Los parámetros predeterminados no funcionarían sin cambiar el sistema de tipos.

Mi conjetura es que las nuevas funciones que la gente escribe en C ++ 11 usualmente tomarán los parámetros de la std::function porque de esta manera, la función no tendrá que ser una plantilla, o no tendrá que crear una instancia para cada lambda. y el funtor que se le pasa.

¿Por qué una std::function (o puntero de función) no puede tomar valores predeterminados?

No es obvio cuál sería el tipo de tal función.

  • Si es std::function<int(bool)> , entonces, ¿cómo lo llamas con los valores predeterminados? (No puedes)

  • Si es std::function<int(bool=false)> , ¿con qué tipos es compatible y cómo funciona la conversión? ¿Puedes convertirlo a std::function<int()> ? ¿Qué pasa con std::function<int(bool=true)> ?

  • Si es algo nuevo como std::function<int(bool=default)> , ¿con qué tipos es compatible y cómo funciona la conversión?

Básicamente, esto no es solo un interruptor que puede activar el estándar y hacer que los punteros de función / std::function manejen los argumentos predeterminados. Los argumentos predeterminados en las funciones normales se manejan usando información de la declaración de la función, que no está disponible en el sitio de la llamada para un puntero lambda o de función. Por lo tanto, tendría que codificar información sobre los valores predeterminados en el tipo de función y luego resolver todas las reglas no obvias para la conversión y la compatibilidad.

Por lo tanto, tendría que presentar un caso convincente para explicar por qué se agregaría esa característica y convencer al comité.

Entonces, ¿por qué los lambdas no pueden tomar los incumplimientos?

No he respondido esta pregunta. Pero no creo que sea una característica muy útil para agregar. Eliminaría esta respuesta si pudiera, pero ha sido aceptada. Lo votaría si pudiera, pero es mío. Así es la vida.