Passer la capture de lambda comme pointeur de fonction

210

Est-il possible de passer une fonction lambda en tant que pointeur de fonction? Si c'est le cas, je dois faire quelque chose de mal car je reçois une erreur de compilation.

Considérez l'exemple suivant

using DecisionFn = bool(*)();

class Decide
{
public:
    Decide(DecisionFn dec) : _dec{dec} {}
private:
    DecisionFn _dec;
};

int main()
{
    int x = 5;
    Decide greaterThanThree{ [x](){ return x > 3; } };
    return 0;
}

Lorsque j'essaie de compiler cela , j'obtiens l'erreur de compilation suivante:

In function 'int main()':
17:31: error: the value of 'x' is not usable in a constant expression
16:9:  note: 'int x' is not const
17:53: error: no matching function for call to 'Decide::Decide(<brace-enclosed initializer list>)'
17:53: note: candidates are:
9:5:   note: Decide::Decide(DecisionFn)
9:5:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'DecisionFn {aka bool (*)()}'
6:7:   note: constexpr Decide::Decide(const Decide&)
6:7:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'const Decide&'
6:7:   note: constexpr Decide::Decide(Decide&&)
6:7:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'Decide&&'

C'est un sacré message d'erreur à digérer, mais je pense que ce que j'en retire, c'est que le lambda ne peut pas être traité comme un constexprdonc je ne peux donc pas le passer comme pointeur de fonction? J'ai aussi essayé de faire xconst, mais cela ne semble pas aider.

Cory Kramer
la source
34
lambda ne peut se désintégrer pour fonctionner comme pointeur que s'il ne capture rien.
Jarod42
Pour la postérité, le billet de blog lié ci-dessus vit maintenant sur devblogs.microsoft.com/oldnewthing/20150220-00/?p=44623
warrenm

Réponses:

205

Un lambda ne peut être converti en pointeur de fonction que s'il ne capture pas, à partir du projet de section standard C ++ 11 5.1.2 [expr.prim.lambda] dit ( accent sur le mien ):

Le type de fermeture pour une expression lambda sans capture lambda a une fonction de conversion de const publique non virtuelle non explicite pour pointer vers une fonction ayant le même paramètre et renvoyer les types que l'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'invocation de l'opérateur d'appel de fonction du type de fermeture.

Remarque, cppreference couvre également cela dans leur section sur les fonctions Lambda .

Les alternatives suivantes fonctionneraient donc:

typedef bool(*DecisionFn)(int);

Decide greaterThanThree{ []( int x ){ return x > 3; } };

et il en serait de même:

typedef bool(*DecisionFn)();

Decide greaterThanThree{ [](){ return true ; } };

et comme le souligne 5gon12eder , vous pouvez également l'utiliser std::function, mais notez que std::functionc'est un poids lourd , donc ce n'est pas un compromis moins coûteux.

Shafik Yaghmour
la source
2
Note latérale: Une solution courante utilisée par les trucs C est de passer un void*comme seul paramètre. Il est normalement appelé "pointeur utilisateur". Il est également relativement léger, mais a tendance à nécessiter de l' mallocespace.
Fund Monica's Lawsuit
94

La réponse de Shafik Yaghmour explique correctement pourquoi le lambda ne peut pas être passé en tant que pointeur de fonction s'il a une capture. Je voudrais montrer deux correctifs simples pour le problème.

  1. Utilisez à la std::functionplace des pointeurs de fonction bruts.

    Ceci est une solution très propre. Notez cependant qu'il inclut une surcharge supplémentaire pour l'effacement de type (probablement un appel de fonction virtuelle).

    #include <functional>
    #include <utility>
    
    struct Decide
    {
      using DecisionFn = std::function<bool()>;
      Decide(DecisionFn dec) : dec_ {std::move(dec)} {}
      DecisionFn dec_;
    };
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree { [x](){ return x > 3; } };
    }
  2. Utilisez une expression lambda qui ne capture rien.

    Étant donné que votre prédicat n'est vraiment qu'une constante booléenne, les éléments suivants contourneraient rapidement le problème actuel. Voir cette réponse pour une bonne explication pourquoi et comment cela fonctionne.

    // Your 'Decide' class as in your post.
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree {
        (x > 3) ? [](){ return true; } : [](){ return false; }
      };
    }
5gon12eder
la source
4
@TC Voir cette question pour savoir pourquoi cela fonctionne
Shafik Yaghmour
Notez qu'en général, si vous connaissez les données de capture au moment de la compilation, vous pouvez les convertir en données de type, puis vous êtes de retour à avoir un lambda sans capture - voir cette réponse que je viens d'écrire à une autre question (merci à @ Réponse de 5gon12eder ici).
dan-man
L'objet ne devrait-il pas alors avoir une durée de vie plus longue que la fonction pointeur? Je voudrais l'utiliser pour glutReshapeFunc.
ar2015
je ne recommande pas cette suggestion, les choses qui ont tendance à fonctionner comme par magie, introduisent de nouvelles erreurs. et les pratiques qui vont de pair avec ces erreurs. si vous voulez utiliser std :: function, vous devriez voir toutes sortes de façons dont std :: function peut être utilisé. parce que d'une certaine façon, peut-être quelque chose que vous ne voulez pas.
TheNegative
1
Cela ne répond pas à la question. Si l'on pouvait utiliser std::functionou un lambda - pourquoi ne le feraient-ils pas? À tout le moins, c'est une syntaxe plus lisible. Habituellement, il faut utiliser un pointeur de fonction pour interagir avec les bibliothèques C (en fait, avec n'importe quelle bibliothèque externe) , et bien sûr vous ne pouvez pas le modifier pour accepter une fonction std :: ou lambda.
Hi-Angel
40

Les expressions lambda, même capturées, peuvent être traitées comme un pointeur de fonction (pointeur vers une fonction membre).

C'est délicat car une expression lambda n'est pas une simple fonction. Il s'agit en fait d'un objet avec un opérateur ().

Lorsque vous êtes créatif, vous pouvez l'utiliser! Pensez à une classe "function" dans le style de std :: function. Si vous enregistrez l'objet, vous pouvez également utiliser le pointeur de fonction.

Pour utiliser le pointeur de fonction, vous pouvez utiliser les éléments suivants:

int first = 5;
auto lambda = [=](int x, int z) {
    return x + z + first;
};
int(decltype(lambda)::*ptr)(int, int)const = &decltype(lambda)::operator();
std::cout << "test = " << (lambda.*ptr)(2, 3) << std::endl;

Pour construire une classe qui peut commencer à fonctionner comme une "std :: function", vous avez d'abord besoin d'une classe / struct qui peut stocker un objet et un pointeur de fonction. Vous avez également besoin d'un opérateur () pour l'exécuter:

// OT => Object Type
// RT => Return Type
// A ... => Arguments
template<typename OT, typename RT, typename ... A>
struct lambda_expression {
    OT _object;
    RT(OT::*_function)(A...)const;

    lambda_expression(const OT & object)
        : _object(object), _function(&decltype(_object)::operator()) {}

    RT operator() (A ... args) const {
        return (_object.*_function)(args...);
    }
};

Avec cela, vous pouvez maintenant exécuter des lambdas capturés et non capturés, tout comme vous utilisez l'original:

auto capture_lambda() {
    int first = 5;
    auto lambda = [=](int x, int z) {
        return x + z + first;
    };
    return lambda_expression<decltype(lambda), int, int, int>(lambda);
}

auto noncapture_lambda() {
    auto lambda = [](int x, int z) {
        return x + z;
    };
    return lambda_expression<decltype(lambda), int, int, int>(lambda);
}

void refcapture_lambda() {
    int test;
    auto lambda = [&](int x, int z) {
        test = x + z;
    };
    lambda_expression<decltype(lambda), void, int, int>f(lambda);
    f(2, 3);

    std::cout << "test value = " << test << std::endl;
}

int main(int argc, char **argv) {
    auto f_capture = capture_lambda();
    auto f_noncapture = noncapture_lambda();

    std::cout << "main test = " << f_capture(2, 3) << std::endl;
    std::cout << "main test = " << f_noncapture(2, 3) << std::endl;

    refcapture_lambda();

    system("PAUSE");
    return 0;
}

Ce code fonctionne avec VS2015

Mise à jour 04.07.17:

template <typename CT, typename ... A> struct function
: public function<decltype(&CT::operator())(A...)> {};

template <typename C> struct function<C> {
private:
    C mObject;

public:
    function(const C & obj)
        : mObject(obj) {}

    template<typename... Args> typename 
    std::result_of<C(Args...)>::type operator()(Args... a) {
        return this->mObject.operator()(a...);
    }

    template<typename... Args> typename 
    std::result_of<const C(Args...)>::type operator()(Args... a) const {
        return this->mObject.operator()(a...);
    }
};

namespace make {
    template<typename C> auto function(const C & obj) {
        return ::function<C>(obj);
    }
}

int main(int argc, char ** argv) {
   auto func = make::function([](int y, int x) { return x*y; });
   std::cout << func(2, 4) << std::endl;
   system("PAUSE");
   return 0;
}
Noxxer
la source
Wouh, c'est dingue! Donc, nous pourrions simplement utiliser les pointeurs internes de la classe lambda (vers l'opérateur de fonction membre ()) pour appeler les lambdas stockés dans une classe wrapper !! INCROYABLE!! Pourquoi avons-nous jamais besoin de la fonction std :: alors? Et est-il possible de faire lambda_expression <decltype (lambda), int, int, int> pour déduire automatiquement / ces paramètres "int" directement à partir du lambda passé lui-même?
barney
2
J'ai ajouté une version courte de mon propre code. cela devrait fonctionner avec une simple fonction auto f = make :: function (lambda); Mais je suis tout à fait sûr que vous trouverez de nombreuses situations où mon code ne fonctionnera pas. std :: function est bien plus bien construit que cela et devrait être la solution quand vous travaillez. Ceci est ici pour l'éducation et l'utilisation personnelle.
Noxxer
14
Cette solution implique d'appeler le lambda via une operator()implémentation, donc si je le lis correctement, je ne pense pas que cela fonctionnerait pour appeler le lambda à l'aide d'un pointeur de fonction de style C , n'est-ce pas? C'est ce que la question initiale demandait.
Remy Lebeau
13
Vous avez affirmé que les lambdas peuvent être traités comme des pointeurs de fonction, ce que vous n'avez pas fait. Vous avez créé un autre objet pour contenir un lambda, ce qui ne fait rien, vous auriez pu simplement utiliser le lambda d'origine.
Passer
9
Ce n'est pas "passer la capture de lambda comme pointeur de fonction". C'est "passer la capture de lambda en tant qu'objet contenant entre autres un pointeur de fonction". Il y a un monde de différence.
n. «pronoms» m.
15

La capture de lambdas ne peut pas être convertie en pointeurs de fonction, comme l'a souligné cette réponse .

Cependant, il est souvent assez difficile de fournir un pointeur de fonction à une API qui n'en accepte qu'un. Pour ce faire, la méthode la plus souvent citée consiste à fournir une fonction et à appeler un objet statique avec elle.

static Callable callable;
static bool wrapper()
{
    return callable();
}

C'est fastidieux. Nous poussons cette idée plus loin et automatisons le processus de création wrapperet nous facilitons la vie.

#include<type_traits>
#include<utility>

template<typename Callable>
union storage
{
    storage() {}
    std::decay_t<Callable> callable;
};

template<int, typename Callable, typename Ret, typename... Args>
auto fnptr_(Callable&& c, Ret (*)(Args...))
{
    static bool used = false;
    static storage<Callable> s;
    using type = decltype(s.callable);

    if(used)
        s.callable.~type();
    new (&s.callable) type(std::forward<Callable>(c));
    used = true;

    return [](Args... args) -> Ret {
        return Ret(s.callable(std::forward<Args>(args)...));
    };
}

template<typename Fn, int N = 0, typename Callable>
Fn* fnptr(Callable&& c)
{
    return fnptr_<N>(std::forward<Callable>(c), (Fn*)nullptr);
}

Et utilisez-le comme

void foo(void (*fn)())
{
    fn();   
}

int main()
{
    int i = 42;
    auto fn = fnptr<void()>([i]{std::cout << i;});
    foo(fn);  // compiles!
}

Vivre

Il s'agit essentiellement de déclarer une fonction anonyme à chaque occurrence de fnptr.

Notez que les invocations de fnptrécraser les callablecallables donnés précédemment écrites du même type. Nous y remédions, dans une certaine mesure, avec le intparamètre N.

std::function<void()> func1, func2;
auto fn1 = fnptr<void(), 1>(func1);
auto fn2 = fnptr<void(), 2>(func2);  // different function
Passer par
la source
forcer le N entier à être déclaré serait une manière élégante de se souvenir du client pour éviter d'écraser les pointeurs de fonction au moment de la compilation.
fiorentinoing
2

Un raccourci pour utiliser un lambda avec comme pointeur de fonction C est le suivant:

"auto fun = +[](){}"

Utilisation de Curl comme exemple ( informations de débogage curl )

auto callback = +[](CURL* handle, curl_infotype type, char* data, size_t size, void*){ //add code here :-) };
curl_easy_setopt(curlHande, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curlHande,CURLOPT_DEBUGFUNCTION,callback);
janCoffee
la source
3
Ce lambda n'a pas de capture. Le problème de l'OP est la capture, sans avoir à déduire le type de pointeur de fonction (c'est ce que l' +astuce vous apporte).
Sneftel
2

Bien que l'approche par modèle soit intelligente pour diverses raisons, il est important de se souvenir du cycle de vie du lambda et des variables capturées. Si une forme quelconque de pointeur lambda est utilisée et que le lambda n'est pas une continuation vers le bas, alors seul un lambda de copie [=] doit être utilisé. C'est-à-dire, même dans ce cas, la capture d'un pointeur vers une variable de la pile n'est PAS SÉCURISÉE si la durée de vie de ces pointeurs capturés (déroulement de la pile) est plus courte que la durée de vie du lambda.

Une solution plus simple pour capturer un lambda en tant que pointeur est:

auto pLamdba = new std::function<...fn-sig...>([=](...fn-sig...){...});

par exemple, new std::function<void()>([=]() -> void {...}

N'oubliez pas de le faire plus tard delete pLamdbaafin de ne pas divulguer la mémoire lambda. Le secret à réaliser ici est que les lambdas peuvent capturer des lambdas (demandez-vous comment cela fonctionne) et aussi que pourstd::function fonctionner de manière générique, l'implémentation lambda doit contenir suffisamment d'informations internes pour donner accès à la taille des données lambda (et capturées) ( c'est pourquoi cela deletedevrait fonctionner [exécuter des destructeurs de types capturés]).

smallscript
la source
Pourquoi s'embêter avec la fonction new- std :: stocke déjà le lambda sur le tas ET évite d'avoir à se rappeler d'appeler delete.
Chris Dodd
0

Pas une réponse directe, mais une légère variation pour utiliser le modèle de modèle "functor" pour cacher les spécificités du type lambda et garder le code agréable et simple.

Je n'étais pas sûr de savoir comment vous vouliez utiliser la classe decide, j'ai donc dû étendre la classe avec une fonction qui l'utilise. Voir l'exemple complet ici: https://godbolt.org/z/jtByqE

La forme de base de votre classe pourrait ressembler à ceci:

template <typename Functor>
class Decide
{
public:
    Decide(Functor dec) : _dec{dec} {}
private:
    Functor _dec;
};

Où vous passez le type de la fonction dans le cadre du type de classe utilisé comme:

auto decide_fc = [](int x){ return x > 3; };
Decide<decltype(decide_fc)> greaterThanThree{decide_fc};

Encore une fois, je ne savais pas pourquoi vous capturez, xil était plus logique (pour moi) d'avoir un paramètre que vous transmettez au lambda) afin que vous puissiez utiliser comme:

int result = _dec(5); // or whatever value

Voir le lien pour un exemple complet

code_fodder
la source
-2

Comme cela a été mentionné par les autres, vous pouvez remplacer la fonction Lambda au lieu du pointeur de fonction. J'utilise cette méthode dans mon interface C ++ vers le solveur F77 ODE RKSUITE.

//C interface to Fortran subroutine UT
extern "C"  void UT(void(*)(double*,double*,double*),double*,double*,double*,
double*,double*,double*,int*);

// C++ wrapper which calls extern "C" void UT routine
static  void   rk_ut(void(*)(double*,double*,double*),double*,double*,double*,
double*,double*,double*,int*);

//  Call of rk_ut with lambda passed instead of function pointer to derivative
//  routine
mathlib::RungeKuttaSolver::rk_ut([](double* T,double* Y,double* YP)->void{YP[0]=Y[1]; YP[1]= -Y[0];}, TWANT,T,Y,YP,YMAX,WORK,UFLAG);
beniekg
la source