El +
en la expresión +[](){}
es el +
operador unario . Se define de la siguiente manera en [expr.unary.op] / 7:
El operando del +
operador unario debe tener enumeración aritmética, sin ámbito o tipo puntero y el resultado es el valor del argumento.
La lambda no es de tipo aritmético, etc., pero se puede convertir:
[expr.prim.lambda] / 3
El tipo de expresión lambda [...] es un tipo de clase sin unión, sin nombre, único, llamado tipo de cierre , cuyas propiedades se describen a continuación.
[expr.prim.lambda] / 6
El tipo de cierre para un lambda-expresión sin lambda de captura tiene un public
no- virtual
no- explicit
const
función de conversión a puntero a la función que tienen los mismos parámetros y valores de retorno como operador de llamada de función del tipo de cierre. El valor devuelto por esta función de conversión será la dirección de una función que, cuando se invoca, tiene el mismo efecto que la invocación del operador de llamada de función del tipo de cierre.
Por lo tanto, el unario +
fuerza la conversión al tipo de puntero de función, que es para esta lambda void (*)()
. Por lo tanto, el tipo de expresión +[](){}
es este tipo de puntero de función void (*)()
.
La segunda sobrecarga se void foo(void (*f)())
convierte en una coincidencia exacta en la clasificación de resolución de sobrecarga y, por lo tanto, se elige sin ambigüedades (ya que la primera sobrecarga NO es una coincidencia exacta).
La lambda [](){}
se puede convertir a std::function<void()>
través de la plantilla no explícita ctor de std::function
, que toma cualquier tipo que cumpla con los requisitos Callable
y CopyConstructible
.
La lambda también se puede convertir a void (*)()
través de la función de conversión del tipo de cierre (ver arriba).
Ambas son secuencias de conversión definidas por el usuario y del mismo rango. Es por eso que la resolución de sobrecarga falla en el primer ejemplo debido a la ambigüedad.
Según Cassio Neri, respaldado por un argumento de Daniel Krügler, este +
truco unario debe ser un comportamiento específico, es decir, puede confiar en él (ver discusión en los comentarios).
Aún así, recomendaría usar una conversión explícita al tipo de puntero de función si desea evitar la ambigüedad: no necesita preguntarle a SO qué hace y por qué funciona;)
std::bind
de unstd::function
objeto que se puede llamar de manera similar a una función lvalue.operator +()
a un tipo de cierre sin estado. Suponga que este operador devuelve algo diferente al puntero a la función a la que se convierte el tipo de cierre. Entonces, esto alteraría el comportamiento observable de un programa que viola 5.1.2 / 3. Por favor, avíseme si está de acuerdo con este razonamiento.operator +
, pero esto se compara con la situación en la que no existeoperator +
para empezar. Pero no se especifica que el tipo de cierre no tendráoperator +
sobrecarga. "Una implementación puede definir el tipo de cierre de manera diferente a lo que se describe a continuación, siempre que esto no altere el comportamiento observable del programa más que por [...]", pero la OMI agregar un operador no cambia el tipo de cierre a algo diferente de lo que se "describe a continuación".operator +()
es exactamente la que describe el estándar. El estándar permite que una implementación haga algo diferente de lo que se especifica. Por ejemplo, agregandooperator +()
. Sin embargo, si esta diferencia es observable por un programa, entonces es ilegal. Una vez pregunté en comp.lang.c ++. Moderado si un tipo de cierre podría agregar un typedef pararesult_type
y el otrotypedefs
requerido para hacerlos adaptables (por ejemplo, porstd::not1
). Me dijeron que no podía porque esto era observable. Intentaré encontrar el enlace.