Fonction renvoyant une expression lambda

88

Je me demande s'il est possible d'écrire une fonction qui renvoie une fonction lambda en C ++ 11. Bien sûr, un problème est de savoir comment déclarer une telle fonction. Chaque lambda a un type, mais ce type n'est pas exprimable en C ++. Je ne pense pas que cela fonctionnerait:

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

Ni ceci:

int(int) retFun();

Je ne suis pas au courant de conversions automatiques de lambdas en, par exemple, des pointeurs vers des fonctions, ou quelque chose du genre. La seule solution est-elle de fabriquer à la main un objet de fonction et de le renvoyer?

Bartosz Milewski
la source
1
Pour ajouter ce qui a déjà été dit, les fonctions lambda sans état sont convertibles en pointeurs de fonction.
snk_kid
3
IMO, votre première option ne fonctionnera pas car le lambda dans le decltypen'est pas le même que dans le corps de la fonction et a donc un type différent (même si vous avez inclus l'instruction de retour)
Motti
1
À propos, si un lambda a une clause de capture vide, il peut être implicitement convertible en un pointeur vers une fonction.
GManNickG
@GMan: Sauf si vous utilisez Visual C ++ 2010 ou une version de g ++ publiée il y a plus d'un an environ (ou à peu près). La conversion implicite captureless-lambda en pointeur de fonction n'a été ajoutée qu'en mars 2010 dans N3092.
James McNellis
4
Les expressions Lambda en général ne peuvent pas apparaître dans des opérandes non évalués. Donc decltype([](){})ou sizeof([]() {})est mal formé, peu importe où vous l'écrivez.
Johannes Schaub - litb

Réponses:

98

Vous n'avez pas besoin d'un objet de fonction fabriqué à la main, utilisez simplement std::function, vers lequel les fonctions lambda sont convertibles:

Cet exemple renvoie la fonction d'identité entière:

std::function<int (int)> retFun() {
    return [](int x) { return x; };
}
Sean
la source
6
Duh! J'adore StackOverflow. Il m'aurait fallu beaucoup plus de temps pour rechercher le sujet puis obtenir la réponse sur StackOverflow. Merci Sean!
Bartosz Milewski
5
Cela va provoquer une allocation de mémoire dans le constructeur de std::function.
Maxim Egorushkin
2
@Maxim Yegorushkin std :: function a une sémantique de déplacement et peut utiliser des allocateurs personnalisés et le brouillon de travail C ++ 0x a ces notes: "[Remarque: les implémentations sont encouragées à éviter l'utilisation de la mémoire allouée dynamiquement pour les petits objets appelables, par exemple , où la cible de f est un objet contenant uniquement un pointeur ou une référence à un objet et un pointeur de fonction membre. —end note] "donc fondamentalement, vous ne pouvez pas faire beaucoup d'hypothèses sur la stratégie d'allocation utilisée par une implémentation particulière, mais vous devriez pouvoir pour utiliser quand même vos propres allocateurs (groupés).
snk_kid
1
@Maxim: Ma réponse était à la question "La seule solution est-elle de fabriquer à la main un objet fonction et de le renvoyer?"
Sean
2
Gardez simplement à l'esprit que l' std::functioneffacement de type est utilisé, ce qui peut entraîner le coût d'un appel de fonction virtuelle lors de l'appel de std::function. Quelque chose à savoir si la fonction retournée va être utilisée dans une boucle interne serrée ou dans un autre contexte où la légère inefficacité compte.
Anthony Hall
29

Pour cet exemple simple, vous n'avez pas besoin de std::function.

À partir du §5.1.2 / 6 standard:

Le type de fermeture pour un lambda-expression sans lambda-capture a un non explicite public non virtuelle fonction de conversion de const pointeur vers une fonction ayant les mêmes paramètres et types de retour comme opérateur d'appel de fonction du type de fermeture. La valeur renvoyée par cette fonction de conversion doit être l'adresse d'une fonction qui, lorsqu'elle est invoquée, a le même effet que l'appel de l'opérateur d'appel de fonction du type de fermeture.

Étant donné que votre fonction n'a pas de capture, cela signifie que le lambda peut être converti en un pointeur vers une fonction de type int (*)(int):

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

C'est ce que je comprends, corrigez-moi si je me trompe.

user534498
la source
1
Cela semble juste. Malheureusement, cela ne fonctionne pas avec le compilateur actuel que j'utilise: VS 2010. La conversion de std :: function fonctionne.
Bartosz Milewski
3
Oui, le libellé final de cette règle est arrivé trop tard pour VC2010.
Ben Voigt
J'ai ajouté un exemple de code. Voici un programme complet .
jfs
@JFSebastian - Quelle est la durée de vie du lambda dans cet exemple? Est-il assez long pour survivre au résultat de la conversion en pointeur de fonction?
Flexo
1
@JFSebastian - Je n'arrive pas à trouver de réponse à cela, alors je l'ai posée comme une question à part entière: stackoverflow.com/questions/8026170/...
Flexo
21

Vous pouvez renvoyer une fonction lambda à partir d'une autre fonction lambda, car vous ne devez pas spécifier explicitement le type de retour de la fonction lambda. Écrivez simplement quelque chose comme ça dans une portée globale:

 auto retFun = []() {
     return [](int x) {return x;};
 };
dzhioev
la source
2
Cela n'est vrai que lorsque le lambda externe se compose uniquement de l'instruction return. Sinon, vous devez spécifier le type de retour.
Bartosz Milewski
3
C'est la meilleure réponse car elle ne nécessite pas de polymorphisme d'exécution de std :: function et permet à lambda d'avoir une liste de capture non vide, cependant j'utiliserais const auto fun = ...
robson3.14 juil.14
2
@BartoszMilewski n'est pas vrai depuis C ++ 14.
dzhioev
19

Bien que la question porte spécifiquement sur C ++ 11, pour le bien des autres qui trébuchent sur cela et ont accès à un compilateur C ++ 14, C ++ 14 permet maintenant des types de retour déduits pour les fonctions ordinaires. Ainsi, l'exemple de la question peut être ajusté juste pour fonctionner comme vous le souhaitez en supprimant simplement la -> decltypeclause ... après la liste des paramètres de la fonction:

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

Notez, cependant, que cela ne fonctionnera pas si plus d'un return <lambda>;apparaît dans la fonction. En effet, une restriction sur la déduction du type de retour est que toutes les instructions de retour doivent renvoyer des expressions du même type, mais chaque objet lambda reçoit son propre type unique par le compilateur, de sorte que les return <lambda>;expressions auront chacune un type différent.

Anthony Hall
la source
4
Pourquoi mentionner les types déduits de C ++ 14 mais omettre les lambdas polymorphes? auto retFun() { return [](auto const& x) { return x; }; }
sehe
1

Vous devriez écrire comme ceci:

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

pour obtenir votre retour en tant que fonction, et utilisez-le comme:

int val = returnFunction(someNumber);
Terens Tare
la source
0

Si vous n'avez pas c ++ 11 et exécutez votre code C ++ sur des micro-contrôleurs par exemple. Vous pouvez renvoyer un pointeur vide, puis effectuer une distribution.

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
la source