Os valores de fechamento lambda podem ser passados ​​como parâmetros de referência rvalue

18

Descobri que os lvaluefechamentos lambda sempre podem ser passados ​​como rvalueparâmetros de função.

Veja a seguinte demonstração simples.

#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;
}

O caso 2 é o comportamento padrão (usei apenas um std::functionpara fins de demonstração, mas qualquer outro tipo se comportaria da mesma maneira).

Como e por que o caso 1 funciona? Qual é o estado de fn1fechamento após o retorno da função?

Sumudu
fonte
5
Eu acho que é porque fn1é implicitamente convertido em um std::functionin foo(fn1). Essa função temporária é então um rvalue.
Eike
@RichardCritten Eu realmente não tinha certeza, então não postei uma resposta. Eu acho que agora não há necessidade de outro.
Eike
11
@eike np Costumo me sentir da mesma maneira e muitas respostas.
Richard Critten 12/11/19
2
@Sumudu A pessoa que fez a pergunta enganou você porque não sabia o que estava tentando perguntar. O que eles pretendiam perguntar era: "Por que os argumentos de modelo não podem std::functionser deduzidos de um lambda". Seu programa não tenta deduzir os argumentos do modelo std::function, portanto, não há problema com a conversão implícita.
eerorika
11
O título da pergunta que você vinculou é um pouco enganador. std::functionpossui um construtor não explícito que aceita fechamentos lambda, portanto, há conversão implícita. Mas nas circunstâncias da questão vinculada, a instanciação de modelo de std::functionnão pode ser inferida a partir do tipo lambda. (Por exemplo, std::function<void()>pode ser construído a partir de [](){return 5;}um tipo de retorno não nulo.
igual a 12/11/19

Respostas:

8

Como e por que o caso 1 funciona?

A chamada foorequer uma instância std::function<void()>que se ligue a uma referência rvalue . std::function<void()>pode ser construído a partir de qualquer objeto que possa ser chamado que seja compatível com a void()assinatura.

Em primeiro lugar, std::function<void()>é construído um objeto temporário []{}. O construtor usado é o número 5 aqui , que copia o fechamento na std::functioninstância:

template< class F >
function( F f );

Inicializa o destino com std::move(f). Se ffor um ponteiro nulo para funcionar ou ponteiro nulo para membro, *thisficará vazio após a chamada.

Em seguida, a functioninstância temporária é vinculada à referência rvalue.


Qual é o estado do fechamento do fn1 após o retorno da função?

O mesmo de antes, porque foi copiado em uma std::functioninstância. O fechamento original não é afetado.

Vittorio Romeo
fonte
8

Um lambda não é um std::function. A referência não liga diretamente .

O caso 1 funciona porque as lambdas são conversíveis em std::functions. Isso significa que um temporário std::functioné materializado pela cópia fn1 . Esse temporário pode ser vinculado a uma referência rvalue e, portanto, o argumento corresponde ao parâmetro.

E a cópia também fn1é por que não é afetada por nada que aconteça foo.

Contador de Histórias - Monica Sem Calúnia
fonte
5

Qual é o estado do fechamento do fn1 após o retorno da função?

fn1 é apátrida, pois não captura nada.

Como e por que o caso 1 funciona?

Funciona porque o argumento é de tipo diferente do tipo que é referenciado por rvalue. Por ter um tipo diferente, as conversões implícitas são consideradas. Como o lambda é Callable para os argumentos disso std::function, é implicitamente convertível a ele através do construtor de conversão de modelo de std::function. O resultado da conversão é um pré-valor e, portanto, pode ser associado à referência de rvalue.

eerorika
fonte