Usando `std::function<void(…)>` para llamar a una función no nula
-
27-10-2019 - |
Pregunta
Hace un tiempo usé std::function
más o menos así:
std::function<void(int)> func = [](int i) -> int { return i; };
Básicamente, hice esto porque quería almacenar diferentes objetos de función en un std::function
, pero no quería restringir los tipos de devolución de estas funciones.Como esto parecía funcionar, lo seguí.Pero no estoy convencido de que sea seguro de usar y no he podido encontrar ninguna documentación al respecto.¿Alguien sabe si este uso es legítimo?O más generalmente, ¿cuáles son las reglas para el objeto que puede asignarse con seguridad a un std::function
?
Editar
Para aclarar, el problema que me preocupa es que la función lambda devuelve un int
, mientras func
se declara con tipo de retorno void
.No estoy seguro de si esto está bien, especialmente después de una llamada a func()
está hecho.
Solución
Su código tiene un comportamiento indefinido. Puede o no funcionar como espere. La razón por la que tiene un comportamiento indefinido es por 20.8.11.2.1 [func.wrap.func.con]/p7:
Requiere:
F
seráCopyConstructible
.f
será llamable (20.8.11.2) para los tipos de argumentosArgTypes
y tipo de retornoR
.
Para f
para ser llamable para el tipo de devolución R
, f
debe devolver algo implícitamente convertible al tipo de retorno del std::function
(void
en tu caso). Y int
no es implícitamente convertible para void
.
Esperaría que su código funcione en la mayoría de las implementaciones. Sin embargo, en al menos una implementación (libc ++), no puede compilar:
test.cpp:7:30: error: no viable conversion from 'int (int)' to 'std::function<void (int)>'
std::function<void(int)> ff = f;
^ ~
Irónicamente, la justificación de este comportamiento proviene de otra pregunta.
La otra pregunta presentó un problema con std::function
uso. La solución a ese problema implicó que la implementación aplique el Requiere: cláusula en el momento de la compilación. En contraste, la solución al problema de esta pregunta es amenazante la implementación de hacer cumplir el Requiere: cláusula.
Otros consejos
Su caso de uso está bien definido según el estándar.
Estás construyendo un std::function
de un objeto invocable[1]
§20.8.11.2.1/7:
template<class F> function(F f);
Requiere:F será CopyConstructible.f será llamable (20.8.11.2) para los tipos de argumentos Argtypes y el tipo de retorno R.
Entonces, ¿tu f callable?
§20.8.11.2/2 dice:
Un objeto llamable F de tipo F es llamable para los tipos de argumentos Argtypes y return Type R si la expresión
INVOKE (f, declval<ArgTypes>()..., R)
, considerado como un operando no evaluado (cláusula 5), está bien formado (20.8.2).
Y la definición de INVOKE
dice:
§20.8.2
Definir
INVOKE (f, t1, t2, ..., tN)
como sigue:...cosas relacionadas con punteros de función miembro/var...—f(t1, t2, ..., tN)
en todos los otros casos.Definir
INVOKE (f, t1, t2, ..., tN, R) as INVOKE (f, t1, t2, ..., tN)
convertido implícitamente aR
.
Y dado que cualquier tipo se puede convertir implícitamente a Como lo señala litb a continuación, no hay una conversión implícita a nula, por lo que esto no está bien definido.void
, su código debería funcionar bien con un compilador que cumpla con los estándares.
[1]:Creo que lambda cuenta aquí como un objeto invocable, aunque no tengo una referencia para eso.Su lambda también podría usarse como puntero de función, ya que no captura ningún contexto.
Parece que puede estar bien para las funciones anónimas.
Cita de http://www.alorelang.org/release/0.5/doc/std_function.html (Esto no es de la biblioteca estándar C ++, sin embargo, parece que están usando algo similar en enlaces a C ++)
Los objetos de función solo se pueden crear con una definición de función, una expresión de funciones anónimas o accediendo a un método límite usando el operador Dot (.).
Otra forma en que esto podría hacerse almacenando el puntero de la función en Auto como se ve aquí: http://en.wikipedia.org/wiki/anonymous_function (Sección C ++)