Funzione che restituisce un'espressione lambda


88

Mi chiedo se sia possibile scrivere una funzione che restituisce una funzione lambda in C ++ 11. Naturalmente un problema è come dichiarare tale funzione. Ogni lambda ha un tipo, ma quel tipo non è esprimibile in C ++. Non penso che funzionerebbe:

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

Né questo:

int(int) retFun();

Non sono a conoscenza di conversioni automatiche da lambda a, diciamo, puntatori a funzioni o qualcosa del genere. L'unica soluzione è creare manualmente un oggetto funzione e restituirlo?


1
Per aggiungere ciò che è già stato detto, le funzioni lambda senza stato sono convertibili in puntatori a funzione.
snk_kid

3
IMO la tua prima opzione non funzionerà poiché lambda in decltypenon è la stessa del corpo della funzione e quindi ha un tipo diverso (anche se hai incluso l'istruzione return)
Motti

1
A proposito, se un lambda ha una clausola di cattura vuota, può essere implicitamente convertibile in un puntatore alla funzione.
GManNickG

@GMan: a meno che non si utilizzi Visual C ++ 2010 o una versione di g ++ rilasciata più di circa un anno fa (o giù di lì). La conversione implicita senza captur-lambda in puntatore a funzione è stata aggiunta fino a marzo 2010 in N3092.
James McNellis

4
Le espressioni lambda in generale non possono essere visualizzate in operandi non valutati. Quindi decltype([](){})o sizeof([]() {})è mal formato, non importa dove lo scrivi.
Johannes Schaub - litb

Risposte:


98

Non hai bisogno di un oggetto funzione artigianale, basta usare std::function, in cui le funzioni lambda sono convertibili:

Questo esempio restituisce la funzione di identità intera:

std::function<int (int)> retFun() {
    return [](int x) { return x; };
}

6
Duh! Adoro StackOverflow. Mi ci sarebbe voluto molto più tempo per ricercare l'argomento e poi ottenere la risposta su StackOverflow. Grazie Sean!
Bartosz Milewski

6
Ciò causerà un'allocazione di memoria anche se nel costruttore di std::function.
Maxim Egorushkin

2
La funzione @Maxim Yegorushkin std :: ha una semantica di spostamento in più può utilizzare allocatori personalizzati e la bozza di lavoro C ++ 0x contiene queste note: "[Nota: le implementazioni sono incoraggiate per evitare l'uso di memoria allocata dinamicamente per piccoli oggetti richiamabili, ad esempio , dove l'obiettivo di f è un oggetto che contiene solo un puntatore o un riferimento a un oggetto e un puntatore a una funzione membro. —end note] "quindi fondamentalmente non puoi fare molte ipotesi su quale strategia di allocazione sta usando una particolare implementazione ma dovresti essere utilizzare comunque i propri allocatori (raggruppati).
snk_kid

1
@ Maxim: la mia risposta è stata alla domanda "L'unica soluzione è creare manualmente un oggetto funzione e restituirlo?"
Sean

2
Tieni presente che std::functionutilizza la cancellazione del tipo, che può significare il costo di effettuare una chiamata di funzione virtuale quando si chiama il file std::function. Qualcosa di cui essere consapevoli se la funzione restituita verrà utilizzata in un ciclo interno stretto o in un altro contesto in cui la leggera inefficienza è importante.
Anthony Hall

29

Per questo semplice esempio, non è necessario std::function.

Dallo standard §5.1.2 / 6:

Il tipo di chiusura per un'espressione lambda senza acquisizione lambda ha una funzione di conversione const pubblica non virtuale non esplicita per puntare a una funzione con gli stessi parametri e tipi restituiti dell'operatore di chiamata di funzione del tipo di chiusura. Il valore restituito da questa funzione di conversione deve essere l'indirizzo di una funzione che, se invocata, ha lo stesso effetto dell'invocazione dell'operatore di chiamata di funzione del tipo di chiusura.

Poiché la tua funzione non ha un'acquisizione, significa che lambda può essere convertito in un puntatore a una funzione di tipo int (*)(int):

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

Questa è la mia comprensione, correggimi se sbaglio.


1
Suona bene. Sfortunatamente, non funziona con il compilatore corrente che sto usando: VS 2010. La conversione std :: function sembra funzionare.
Bartosz Milewski

3
Sì, l'ultima formulazione di questa regola è arrivata troppo tardi per VC2010.
Ben Voigt

Ho aggiunto un esempio di codice. Ecco un programma completo .
jfs

@JFSebastian - Qual è la durata del lambda in questo esempio? È abbastanza lungo da sopravvivere al risultato della conversione in puntatore a funzione?
Flexo

1
@JFSebastian - Non riesco a trovare una risposta a questa domanda, quindi l'ho fatta come domanda a sé stante: stackoverflow.com/questions/8026170/…
Flexo

21

Puoi restituire la funzione lambda da un'altra funzione lambda, poiché non dovresti specificare esplicitamente il tipo di ritorno della funzione lambda. Basta scrivere qualcosa del genere in ambito globale:

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

2
Questo è vero solo quando il lambda esterno consiste solo nell'istruzione return. Altrimenti devi specificare il tipo di ritorno.
Bartosz Milewski

3
Questa è la risposta migliore in quanto non richiede il polimorfismo di runtime di std :: function e consente a lambda di avere un elenco di acquisizione non vuoto, tuttavia utilizzerei const auto fun = ...
robson3.14

2
@BartoszMilewski non è vero da C ++ 14.
dzhioev

19

Sebbene la domanda chieda specificamente di C ++ 11, per il bene di altri che si imbattono in questo e hanno accesso a un compilatore C ++ 14, C ++ 14 ora consente tipi di ritorno dedotti per funzioni ordinarie. Quindi l'esempio nella domanda può essere regolato solo per funzionare come desiderato semplicemente lasciando cadere la -> decltypeclausola ... dopo l'elenco dei parametri della funzione:

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

Notare, tuttavia, che questo non funzionerà se più di uno return <lambda>;appare nella funzione. Questo perché una restrizione sulla deduzione del tipo restituito è che tutte le istruzioni return devono restituire espressioni dello stesso tipo, ma ogni oggetto lambda riceve il proprio tipo univoco dal compilatore, quindi le return <lambda>;espressioni avranno ciascuna un tipo diverso.


4
Perché menzionare i tipi dedotti di c ++ 14 ma omettere i lambda polimorfici? auto retFun() { return [](auto const& x) { return x; }; }
guarda il

1

Dovresti scrivere in questo modo:

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

per ottenere il tuo ritorno come funzione e usalo come:

int val = returnFunction(someNumber);

0

Se non hai c ++ 11 e stai eseguendo il tuo codice c ++ su microcontrollori, ad esempio. Puoi restituire un puntatore vuoto e quindi eseguire un cast.

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();
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.