Wartości wartości zamknięcia lambda można przekazać jako parametry referencyjne wartości

18

Odkryłem, że lvaluezamknięcia lambda zawsze można przekazać jako rvalueparametry funkcji.

Zobacz następującą prostą demonstrację.

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

Przypadek 2 to standardowe zachowanie (właśnie użyłem std::functiondo celów demonstracyjnych, ale każdy inny typ zachowałby się tak samo).

Jak i dlaczego działa przypadek 1? Jaki jest stan fn1zamknięcia po zwróceniu funkcji?

Sumudu
źródło
5
Myślę, że dzieje się tak, ponieważ fn1domyślnie jest konwertowany na std::functionin foo(fn1). Ta tymczasowa funkcja jest wtedy wartością rvalue.
jak
@RichardCritten Naprawdę nie byłem pewien, więc nie opublikowałem odpowiedzi. Myślę, że teraz nie ma potrzeby kolejnego.
jak
1
@eike np Często czuję się tak samo i tak wiele odpowiedzi.
Richard Critten,
2
@ Sumudu Osoba, która zadała pytanie, wprowadziła cię w błąd, ponieważ nie wiedziała, o co próbowała zadać. Chcieli zapytać: „Dlaczego nie std::functionmożna wywnioskować argumentów z lambda”. Twój program nie próbuje wywnioskować argumentów szablonu std::function, więc nie ma problemu z niejawną konwersją.
eerorika
1
Tytuł pytania, które podłączyłeś, jest trochę mylące. std::functionma nieprecyzyjny konstruktor, który akceptuje zamknięcia lambda, więc zachodzi niejawna konwersja. Ale w okolicznościach połączonego pytania std::functionnie można wywnioskować z wystąpienia lambda typu szablonu . (Na przykład std::function<void()>można skonstruować z [](){return 5;}tego, że ma on nieważny typ zwrotu.
np.

Odpowiedzi:

8

Jak i dlaczego działa przypadek 1?

Wywołanie foowymaga wystąpienia std::function<void()>tego powiązania z odwołaniem do wartości . std::function<void()>można zbudować z dowolnego obiektu, który można wywołać, który jest zgodny z void()podpisem.

Po pierwsze, std::function<void()>budowany jest obiekt tymczasowy []{}. Użyto konstruktora tutaj # 5 , który kopiuje zamknięcie do std::functioninstancji:

template< class F >
function( F f );

Inicjuje cel za pomocą std::move(f). Jeśli fwskaźnik zerowy do funkcji lub wskaźnik zerowy do elementu, *thisbędzie pusty po wywołaniu.

Następnie functioninstancja tymczasowa jest powiązana z odwołaniem do wartości.


Jaki jest stan zamknięcia fn1 po zwróceniu funkcji?

To samo co poprzednio, ponieważ zostało skopiowane do std::functioninstancji. Oryginalne zamknięcie pozostaje nienaruszone.

Vittorio Romeo
źródło
8

Lambda nie jest std::function. Odwołanie nie wiąże się bezpośrednio .

Przypadek 1 działa, ponieważ lambda można zamienić na std::functions. Oznacza to, że tymczasowe std::functionjest zmaterializowane przez kopiowanie fn1 . Wspomniany tymczasowy może być powiązany z odwołaniem do wartości, a zatem argument pasuje do parametru.

A kopiowanie jest również powodem, dla fn1którego nic nie dzieje się w ogóle foo.

StoryTeller - Unslander Monica
źródło
5

Jaki jest stan zamknięcia fn1 po zwróceniu funkcji?

fn1 jest bezpaństwowcem, ponieważ niczego nie chwyta.

Jak i dlaczego działa przypadek 1?

Działa, ponieważ argument jest innego typu niż typ, do którego odwołuje się wartość. Ze względu na inny typ rozważane są niejawne konwersje. Ponieważ lambda jest wywoływalna dla argumentów tego std::function, jest domyślnie konwertowalna na nią za pomocą konstruktora konwertującego szablon std::function. Wynikiem konwersji jest prvalue, a zatem można ją powiązać z referencją wartości.

eerorika
źródło