web-dev-qa-db-fra.com

Passer capturer lambda en tant que pointeur de fonction

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 manière incorrecte 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;
}

Quand je essaie de compiler ceci , 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 déduis, c’est que le lambda ne peut pas être traité comme un constexpr et que je ne peux donc pas le passer comme pointeur de fonction? J'ai aussi essayé de créer x const, mais cela ne semble pas aider.

149
CoryKramer

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

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

Notez que cppreference couvre également cela dans leur section sur Fonctions Lambda .

Donc, les alternatives suivantes fonctionneraient:

typedef bool(*DecisionFn)(int);

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

et ceci aussi:

typedef bool(*DecisionFn)();

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

et comme 5gon12eder fait remarquer, vous pouvez également utiliser std::function , mais notez que std::function est lourd , ce qui ne représente donc pas un compromis sans coût.

158
Shafik Yaghmour

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

  1. Utilisez std::function au lieu des pointeurs de fonction bruts.

    C'est une solution très propre. Notez cependant que cela inclut une surcharge supplémentaire pour le type effacement (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 en réalité 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; }
      };
    }
    
73
5gon12eder

Je sais cela un peu vieux ..

Mais je voulais ajouter: 

L'expression lambda (même capturée) peut être traitée comme un pointeur de fonction!

C'est délicat car une expression Lambda n'est pas une simple fonction. C'est en fait 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 puisse commencer à fonctionner comme un "std :: function", je vais juste faire un exemple court. Vous avez d’abord besoin d’une classe/structure capable de stocker un pointeur de fonction et d’objet, ainsi que 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, 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 J'espère que cela vous aidera:)

Salue!

Modification: modèle de modèle d'aiguilles supprimé, paramètre de pointeur de fonction supprimé, renommé expression_lambda

Mise à jour du 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;
}
26
Noxxer

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

Cependant, il est souvent très pénible de fournir un pointeur de fonction à une API qui n'en accepte qu'un. La méthode la plus souvent citée est de fournir une fonction et d'appeler un objet statique avec.

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

C'est fastidieux. Nous allons plus loin dans cette idée et automatisons le processus de création de wrapper et simplifions beaucoup 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 l'utiliser comme

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

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

Vivre

Cela consiste essentiellement à déclarer une fonction anonyme à chaque occurrence de fnptr.

Notez que les invocations de fnptr écrasent les __ appelables callable écrits précédemment écrits du même type. Nous y remédions, dans une certaine mesure, avec le paramètre int, N.

std::function<void()> func1, func2;
auto fn1 = fnptr<void(), 1>(func1);
auto fn2 = fnptr<void(), 2>(func2);  // different function
7
Passer By

Voici un raccourci pour utiliser un lambda avec un pointeur de fonction C:

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

Utilisation de Curl comme exemple ( informations de débogage de 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);
0
janCoffee

Comme cela a été mentionné par les autres, vous pouvez substituer la fonction Lambda au lieu du pointeur de la fonction. J'utilise cette méthode dans mon interface C++ pour le ré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);
0
beniekg