Funkcja zwracająca wyrażenie lambda

88

Zastanawiam się, czy można napisać funkcję zwracającą funkcję lambda w C ++ 11. Oczywiście problemem jest zadeklarowanie takiej funkcji. Każda lambda ma typ, ale tego typu nie można wyrazić w C ++. Myślę, że to nie zadziała:

auto retFun() -> decltype ([](int x) -> int)
{
    return [](int x) { return x; }
}

Ani to:

int(int) retFun();

Nie jestem świadomy żadnych automatycznych konwersji z lambd na, powiedzmy, wskaźników do funkcji lub podobnych. Czy jedynym rozwiązaniem jest ręczne tworzenie obiektu funkcji i zwracanie go?

Bartosz Milewski
źródło
1
Aby dodać to, co już zostało powiedziane, bezstanowe funkcje lambda można konwertować na wskaźniki funkcji.
snk_kid
3
IMO, twoja pierwsza opcja nie zadziała, ponieważ lambda w funkcji decltypenie jest taka sama jak w treści funkcji i dlatego ma inny typ (nawet jeśli uwzględnisz instrukcję return)
Motti
1
Nawiasem mówiąc, jeśli lambda ma pustą klauzulę przechwytywania, może być niejawnie konwertowana na wskaźnik do funkcji.
GManNickG
@GMan: Chyba że używasz Visual C ++ 2010 lub wersji g ++ wydanej ponad rok temu (lub mniej więcej). Niejawna konwersja bez przechwytywania lambda do wskaźnika funkcji została dodana dopiero w marcu 2010 r. W N3092.
James McNellis,
4
Wyrażenia lambda generalnie nie mogą pojawiać się w nieocenionych operandach. Tak więc decltype([](){})lub sizeof([]() {})jest źle sformułowane, bez względu na to, gdzie to piszesz.
Johannes Schaub - litb

Odpowiedzi:

98

Nie potrzebujesz ręcznie std::functiontworzonego obiektu funkcyjnego, po prostu użyj , na który funkcje lambda są konwertowane:

Ten przykład zwraca funkcję tożsamości w postaci liczby całkowitej:

std::function<int (int)> retFun() {
    return [](int x) { return x; };
}
Sean
źródło
6
Duh! Uwielbiam StackOverflow. Zajęłoby mi dużo więcej czasu, aby zbadać ten temat, a następnie uzyskać odpowiedź na StackOverflow. Dzięki Sean!
Bartosz Milewski
6
To spowoduje alokację pamięci w konstruktorze std::function.
Maxim Egorushkin
2
@Maxim Yegorushkin std :: function ma semantykę przenoszenia oraz może używać niestandardowych alokatorów, a wersja robocza C ++ 0x ma następujące uwagi: "[Uwaga: implementacje są zachęcane do unikania używania dynamicznie przydzielanej pamięci dla małych wywoływanych obiektów, na przykład , gdzie celem f jest obiekt przechowujący tylko wskaźnik lub odniesienie do obiektu i wskaźnika funkcji składowej. —end note] ”, więc w zasadzie nie możesz poczynić wielu założeń co do strategii alokacji, której używa dana implementacja, ale powinieneś i tak używać własnych (połączonych) alokatorów.
snk_kid
1
@Maxim: Moja odpowiedź brzmiała na pytanie "Czy jedynym rozwiązaniem jest ręczne tworzenie obiektu funkcyjnego i zwracanie go?"
Sean,
2
Należy tylko pamiętać, że std::functionwykorzystuje wymazywanie typów, co może oznaczać koszt wykonania wirtualnego wywołania funkcji podczas wywoływania std::function. Coś, o czym należy pamiętać, jeśli zwrócona funkcja będzie używana w ścisłej pętli wewnętrznej lub w innym kontekście, w którym niewielka nieefektywność ma znaczenie.
Anthony Hall
29

W tym prostym przykładzie nie potrzebujesz std::function.

Od standardu §5.1.2 / 6:

Typ zamknięcia dla wyrażenia lambda bez przechwytywania lambda ma publiczną niewirtualną, niejawną funkcję konwersji const na wskaźnik do funkcji mającej ten sam parametr i typy zwracane, co operator wywołania funkcji typu zamknięcia. Wartość zwracana przez tę funkcję konwersji będzie adresem funkcji, która po wywołaniu ma taki sam skutek jak wywołanie operatora wywołania funkcji typu zamknięcia.

Ponieważ twoja funkcja nie ma przechwytywania, oznacza to, że lambda można przekonwertować na wskaźnik do funkcji typu int (*)(int):

typedef int (*identity_t)(int); // works with gcc
identity_t retFun() { 
  return [](int x) { return x; };
}

Tak rozumiem, popraw mnie, jeśli się mylę.

user534498
źródło
1
Brzmi dobrze. Niestety, nie działa z obecnym kompilatorem, którego używam: VS 2010. Konwersja std :: function zdarza się działać.
Bartosz Milewski
3
Tak, ostateczne sformułowanie tej zasady przyszło za późno na VC2010.
Ben Voigt,
Dodałem przykład kodu. Oto pełny program .
jfs
@JFSebastian - Jaka jest długość życia lambda w tym przykładzie? Czy jest wystarczająco długo, aby przeżyć wynik konwersji na wskaźnik funkcji?
Flexo
1
@JFSebastian - Nie mogę znaleźć odpowiedzi na to pytanie, więc zadałem to samo pytanie: stackoverflow.com/questions/8026170/ ...
Flexo
21

Możesz zwrócić funkcję lambda z innej funkcji lambda, ponieważ nie powinieneś jawnie określać zwracanego typu funkcji lambda. Po prostu napisz coś takiego w zakresie globalnym:

 auto retFun = []() {
     return [](int x) {return x;};
 };
dzhioev
źródło
2
Jest to prawdą tylko wtedy, gdy zewnętrzna lambda składa się tylko z instrukcji return. W przeciwnym razie musisz określić typ zwrotu.
Bartosz Milewski
3
To najlepsza odpowiedź, ponieważ nie wymaga polimorfizmu std :: function w czasie wykonywania i pozwala lambdzie mieć niepustą listę przechwytywania, jednak
użyłbym
2
@BartoszMilewski nie jest prawdziwe od C ++ 14.
dzhioev
19

Chociaż pytanie dotyczy konkretnie C ++ 11, ze względu na innych, którzy się na to natkną i mają dostęp do kompilatora C ++ 14, C ++ 14 pozwala teraz na wywnioskowane typy zwracane dla zwykłych funkcji. Tak więc przykład w pytaniu można dostosować tak, aby działał zgodnie z oczekiwaniami, po prostu upuszczając -> decltypeklauzulę ... po liście parametrów funkcji:

auto retFun()
{
    return [](int x) { return x; }
}

Należy jednak pamiętać, że to nie zadziała, jeśli return <lambda>;w funkcji pojawi się więcej niż jeden . Dzieje się tak, ponieważ ograniczenie dotyczące odliczania typu zwracanego polega na tym, że wszystkie instrukcje return muszą zwracać wyrażenia tego samego typu, ale każdy obiekt lambda otrzymuje od kompilatora swój własny, unikalny typ, więc return <lambda>;każde wyrażenie będzie miało inny typ.

Anthony Hall
źródło
4
Po co wymieniać wywnioskowane typy c ++ 14, ale pomijać polimorficzne lambdy? auto retFun() { return [](auto const& x) { return x; }; }
sehe
1

Powinieneś napisać tak:

auto returnFunction = [](int x){
    return [&x](){
        return x;
    }();
};

aby otrzymać zwrot jako funkcję i użyj go w ten sposób:

int val = returnFunction(someNumber);
Terens Tare
źródło
0

Jeśli nie masz C ++ 11 i uruchamiasz swój kod C ++ na przykład na mikrokontrolerach. Możesz zwrócić void pointer, a następnie wykonać rzut.

void* functionThatReturnsLambda()
{
    void(*someMethod)();

    // your lambda
    someMethod = []() {

        // code of lambda

    };

    return someMethod;
}


int main(int argc, char* argv[])
{

    void* myLambdaRaw = functionThatReturnsLambda();

    // cast it
    auto myLambda = (void(*)())myLambdaRaw;

    // execute lambda
    myLambda();
}
Tono Nam
źródło