Los valores de cierre de Lambda se pueden pasar como parámetros de referencia de valor

18

Descubrí que los lvaluecierres lambda siempre se pueden pasar como rvalueparámetros de función.

Vea la siguiente demostración simple.

#include <iostream>
#include <functional>

using namespace std;

void foo(std::function<void()>&& t)
{
}

int main()
{
    // Case 1: passing a `lvalue` closure
    auto fn1 = []{};
    foo(fn1);                          // works

    // Case 2: passing a `lvalue` function object
    std::function<void()> fn2 = []{};
    foo(fn2);                          // compile error

    return 0;
}

El caso 2 es el comportamiento estándar (acabo de utilizar un std::functionpara fines de demostración, pero cualquier otro tipo se comportaría igual).

¿Cómo y por qué funciona el caso 1? ¿Cuál es el estado de fn1cierre después de que la función regresó?

Sumudu
fuente
55
Supongo que es porque fn1se convierte implícitamente a una std::functionen foo(fn1). Esa función temporal es entonces un valor r.
Eike
@RichardCritten Realmente no estaba seguro, así que no publiqué una respuesta. Creo que ahora no hay necesidad de otro.
Eike
1
@eike np A menudo siento lo mismo y muchas respuestas.
Richard Critten el
2
@Sumudu La persona que hizo la pregunta lo engañó porque no sabía lo que estaba tratando de preguntar. Lo que querían preguntar era: "¿Por qué no std::functionpueden deducirse los argumentos de plantilla de una lambda?". Su programa no intenta deducir los argumentos de la plantilla std::function, por lo que no hay problema con la conversión implícita.
eerorika
1
El título de la pregunta que ha vinculado es un poco engañoso. std::functiontiene un constructor no explícito que acepta cierres lambda, por lo que hay una conversión implícita. Pero en las circunstancias de la pregunta vinculada, la instanciación de plantilla de std::functionno se puede inferir del tipo lambda. (Por ejemplo, std::function<void()>se puede construir [](){return 5;}a pesar de que tiene un tipo de retorno no nulo.
cada

Respuestas:

8

¿Cómo y por qué funciona el caso 1?

La invocación foorequiere una instancia std::function<void()>que se una a una referencia de valor . std::function<void()>se puede construir a partir de cualquier objeto invocable que sea compatible con la void()firma.

En primer lugar, std::function<void()>se construye un objeto temporal []{}. El constructor utilizado es el # 5 aquí , que copia el cierre en la std::functioninstancia:

template< class F >
function( F f );

Inicializa el objetivo con std::move(f). Si fes un puntero nulo para funcionar o un puntero nulo para miembro, *thisestará vacío después de la llamada.

Entonces, la functioninstancia temporal está vinculada a la referencia rvalue.


¿Cuál es el estado del cierre de fn1 después de que la función regresó?

Igual que antes, porque se copió en una std::functioninstancia. El cierre original no se ve afectado.

Vittorio Romeo
fuente
8

Una lambda no es un std::function. La referencia no se une directamente .

El caso 1 funciona porque las lambdas son convertibles a std::functions. Esto significa que un std::functionmaterial temporal se materializa copiando fn1 . Dicho temporal puede vincularse a una referencia de valor r, por lo que el argumento coincide con el parámetro.

Y la copia también es el motivo por el cual no fn1se ve afectado por nada de lo que sucede foo.

StoryTeller - Unslander Monica
fuente
5

¿Cuál es el estado del cierre de fn1 después de que la función regresó?

fn1 no tiene estado, ya que no captura nada.

¿Cómo y por qué funciona el caso 1?

Funciona porque el argumento es de un tipo diferente al tipo al que se hace referencia rvalue. Debido a que tiene un tipo diferente, se consideran las conversiones implícitas. Dado que el lambda es invocable por los argumentos de esto std::function, es implícitamente convertible a él a través del constructor de conversión de plantillas de std::function. El resultado de la conversión es un prvalue y, por lo tanto, puede vincularse con la referencia rvalue.

eerorika
fuente