Przekazywanie przechwytywania lambda jako wskaźnika funkcji

Czy można przekazać funkcję lambda jako wskaźnik funkcji? Jeśli tak, to muszę robić coś nieprawidłowo, ponieważ dostaję błąd kompilacji.

Rozważ następujący przykład

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;
}

Kiedy próbuję skompilować to , pojawia się następujący błąd kompilacji:

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&&'

To jedna wielka wiadomość o błędzie do przetrawienia, ale myślę, że to, co z tego wyciągam, to to, że lambda nie może być traktowana jako constexpr, więc nie mogę jej przekazać jako wskaźnik funkcji? Próbowałem zrobić x const, ale to nie pomaga.

Author: Deduplicator, 2015-02-26

9 answers

Lambda może być przekonwertowana na wskaźnik funkcji tylko wtedy, gdy nie przechwyci, z sekcji draft C++11 standard 5.1.2 [expr.prim.lambda] says ():

Typ zamknięcia dla wyrażenia lambda bez przechwytywania lambda ma public non-virtual non-explicit const funkcja konwersji na wskaźnik do funkcji o tych samych parametrach i typach powrotu co zamknięcie Operator wywołania funkcji typu. Na wartość zwracana przez tę konwersję funkcja jest adresem funkcji, która po wywołaniu ma taki sam efekt jak wywołanie operatora wywołania funkcji typu zamknięcia.

Uwaga, cppreference obejmuje to również w ich sekcji na funkcje Lambda .

Więc następujące alternatywy będą działać:

typedef bool(*DecisionFn)(int);

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

I tak by było:

typedef bool(*DecisionFn)();

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

I jak wskazuje 5gon12eder, Możesz również użyć std::function, ale zauważ, że std::function na duża waga , więc nie jest to kompromis mniej kosztowny.

 229
Author: Shafik Yaghmour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-14 17:22:28

Odpowiedź Shafika Yaghmoura poprawnie wyjaśnia, dlaczego lambda nie może być przekazana jako wskaźnik funkcji, jeśli ma przechwytywanie. Chciałbym pokazać dwie proste poprawki problemu.

  1. Użyj std::function zamiast nieprzetworzonych wskaźników funkcji.

    To bardzo czyste rozwiązanie. Zauważ jednak, że zawiera dodatkowe narzuty dla typu erasure (prawdopodobnie wirtualne wywołanie funkcji).
    #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. Użyj wyrażenia lambda, które nie uchwycić wszystko.

    Ponieważ twój predykat jest tak naprawdę tylko stałą logiczną, poniższe informacje szybko obejdą bieżący problem. Zobacz ta odpowiedź aby uzyskać dobre wyjaśnienie, dlaczego i jak to działa.

    // Your 'Decide' class as in your post.
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree {
        (x > 3) ? [](){ return true; } : [](){ return false; }
      };
    }
    
 106
Author: 5gon12eder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-08 13:30:40

Wyrażenia Lambda, nawet przechwycone, mogą być traktowane jako wskaźnik funkcji (wskaźnik do funkcji członka).

Jest to trudne, ponieważ wyrażenie lambda nie jest prostą funkcją. W rzeczywistości jest to obiekt z operatorem (). Kiedy jesteś kreatywny, możesz tego użyć! Pomyśl o klasie "function" w stylu std::function. Jeśli zapiszesz obiekt, Możesz również użyć wskaźnika funkcji.

Aby użyć wskaźnika funkcji, możesz użyć:

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;

To zbuduj klasę, która może zacząć działać jak "STD:: function" , najpierw potrzebujesz klasy / struktury, niż może przechowywać obiekt i wskaźnik funkcji. Do jego wykonania potrzebny jest również operator ():

// 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...);
    }
};

Dzięki temu możesz teraz uruchomić przechwycone, nie przechwycone lambdy, tak jak używasz oryginału:

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;
}

Ten kod działa z VS2015

Aktualizacja 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;
}
 47
Author: Noxxer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-23 15:30:51

Przechwytywanie lambda nie może być przekonwertowane na Wskaźniki funkcji, ponieważ ta odpowiedź wskazała na to.

Jednak dostarczanie wskaźnika funkcji do API, które akceptuje tylko jeden, jest często dość uciążliwe. Najczęściej cytowaną metodą jest dostarczenie funkcji i wywołanie za jej pomocą obiektu statycznego.

static Callable callable;
static bool wrapper()
{
    return callable();
}
To jest nużące. Podejmujemy tę ideę dalej i automatyzujemy proces tworzenia wrapper i znacznie ułatwiamy życie.
#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);
}

I użyj go as

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

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

Live

Jest to zasadniczo deklarowanie funkcji anonimowej przy każdym wystąpieniu fnptr.

Zauważ, że wywołania fnptr zastępują wcześniej zapisane callable wywołania tego samego typu. Naprawiamy to w pewnym stopniu za pomocą parametru int N.

std::function<void()> func1, func2;
auto fn1 = fnptr<void(), 1>(func1);
auto fn2 = fnptr<void(), 2>(func2);  // different function
 19
Author: Passer By,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-20 13:16:30

Skrót do używania lambda ze wskaźnikiem funkcji C jest następujący:

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

Używanie Curl jako exmample (curl debug info )

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);
 2
Author: janCoffee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-05-15 09:03:05

Nie jest to bezpośrednia odpowiedź, ale niewielka zmiana w użyciu wzorca szablonu "functor", aby ukryć specyfikę typu lambda i utrzymać kod ładny i prosty.

Nie byłem pewien, jak chcesz użyć klasy decide, więc musiałem rozszerzyć klasę o funkcję, która jej używa. Zobacz pełny przykład tutaj: https://godbolt.org/z/jtByqE

Podstawowa forma twojej klasy może wyglądać tak:

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

Gdzie przekazujesz typ funkcji jako część class type used like:

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

Ponownie nie byłem pewien, dlaczego przechwytywasz x bardziej sensowne (dla mnie) było posiadanie parametru, który PRZEKAZUJESZ do lambda), więc możesz użyć w stylu:

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

Zobacz link do pełnego przykładu

 2
Author: code_fodder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-30 13:20:27

Chociaż podejście szablonowe jest sprytne z różnych powodów, ważne jest, aby pamiętać o cyklu życia lambda i przechwyconych zmiennych. Jeśli zostanie użyta jakakolwiek forma wskaźnika lambda, a lambda nie jest kontynuacją w dół, wówczas należy użyć tylko kopiowania [ = ] lambda. Nawet wtedy przechwytywanie wskaźnika do zmiennej na stosie jest niebezpieczne, jeśli żywotność tych przechwyconych wskaźników (odwijanie stosu) jest krótsza niż żywotność lambda.

Prostszy rozwiązanie do przechwytywania lambda jako wskaźnika to:

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

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

Po prostu pamiętaj, aby później delete pLamdba aby upewnić się, że nie wyciek pamięci lambda. Tajemnicą jest to, że lambda może przechwytywać lambda (zadaj sobie pytanie, Jak to działa), a także to, że aby std::function działało ogólnie, implementacja lambda musi zawierać wystarczające wewnętrzne informacje, aby zapewnić dostęp do rozmiaru danych lambda (i przechwyconych). destruktory przechwyconych typów]).

 2
Author: smallscript,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-11 19:09:28

Symulowana odpowiedź, ale zrobiłem ją tak, aby nie trzeba było określać typu zwracanego wskaźnika (zauważ, że wersja generyczna wymaga C++20):

#include <iostream>


template<typename Function>
struct function_traits;

template <typename Ret, typename... Args>
struct function_traits<Ret(Args...)> {
    typedef Ret(*ptr)(Args...);
};

template <typename Ret, typename... Args>
struct function_traits<Ret(*const)(Args...)> : function_traits<Ret(Args...)> {};

template <typename Cls, typename Ret, typename... Args>
struct function_traits<Ret(Cls::*)(Args...) const> : function_traits<Ret(Args...)> {};

using voidfun = void(*)();

template <typename F>
voidfun lambda_to_void_function(F lambda) {
    static auto lambda_copy = lambda;

    return []() {
        lambda_copy();
    };
}

// requires C++20
template <typename F>
auto lambda_to_pointer(F lambda) -> typename function_traits<decltype(&F::operator())>::ptr {
    static auto lambda_copy = lambda;
    
    return []<typename... Args>(Args... args) {
        return lambda_copy(args...);
    };
}



int main() {
    int num;

    void(*foo)() = lambda_to_void_function([&num]() {
        num = 1234;
    });
    foo();
    std::cout << num << std::endl; // 1234

    int(*bar)(int) = lambda_to_pointer([&](int a) -> int {
        num = a;
        return a;
    });
    std::cout << bar(4321) << std::endl; // 4321
    std::cout << num << std::endl; // 4321
}
 -1
Author: Bamberghh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-10 23:04:22

Jak wspominali inni, można zastąpić funkcję Lambda zamiast funkcji pointer. Używam tej metody w moim interfejsie C++ do F77 ODE solver 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);
 -2
Author: beniekg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-19 18:42:54