Przekazywanie przechwytywania lambda jako wskaźnika funkcji

210

Czy można przekazać funkcję lambda jako wskaźnik funkcji? Jeśli tak, to muszę robić coś niepoprawnie, ponieważ pojawia się 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ę to skompilować , 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 jest jeden komunikat o błędzie do przeczytania, ale myślę, że z tego wyciągam to, że lambda nie może być traktowana jako constexprtak, więc nie mogę przekazać go jako wskaźnika funkcji? Próbowałem również stworzyć xconst, ale to nie pomaga.

Cory Kramer
źródło
34
lambda może rozpaść się do działania wskaźnika tylko wtedy, gdy niczego nie przechwyci.
Jarod42
Dla potomnych powyższy link do blogu jest teraz dostępny na devblogs.microsoft.com/oldnewthing/20150220-00/?p=44623
warrenm

Odpowiedzi:

205

Lambda może zostać przekonwertowana na wskaźnik funkcji tylko wtedy, gdy nie jest przechwytywany, ze standardowej sekcji C ++ 11 5.1.2 [expr.prim.lambda] mówi ( wyróżnienie moje ):

Typ zamknięcia dla wyrażenia lambda bez przechwytywania lambda ma publiczną, nie-wirtualną, niejawną funkcję konwersji const na wskaźnik do funkcji posiadającej ten sam parametr i typy zwracane, co operator wywołania funkcji typu zamknięcia. Wartością zwracaną przez tę funkcję konwersji jest adres funkcji, która po wywołaniu ma taki sam efekt, jak wywołanie operatora wywołania funkcji typu zamknięcia.

Zauważ, że cppreference obejmuje to również w swojej sekcji na temat funkcji Lambda .

Więc działałyby następujące alternatywy:

typedef bool(*DecisionFn)(int);

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

i tak by to:

typedef bool(*DecisionFn)();

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

i jak wskazuje 5gon12eder , możesz także użyć std::function, ale pamiętaj, że std::functionjest to duża waga , więc nie jest to kompromis bez kosztów.

Shafik Yaghmour
źródło
2
Uwaga dodatkowa: Jednym z powszechnych rozwiązań używanych w C jest przekazywanie void*jako jedynego parametru. Zwykle nazywany jest „wskaźnikiem użytkownika”. Jest również stosunkowo lekki, ale zwykle wymaga malloctrochę miejsca.
Pozew Fund Moniki w
94

Odpowiedź Shafik Yaghmour za poprawnie wyjaśnia, dlaczego lambda nie mogą być przekazywane jako wskaźnik funkcji, jeśli ma ona przechwytywanie. Chciałbym pokazać dwie proste poprawki dla problemu.

  1. Użyj std::functionzamiast surowych wskaźników funkcji.

    To bardzo czyste rozwiązanie. Należy jednak pamiętać, że zawiera on pewne dodatkowe koszty związane z usuwaniem typu (prawdopodobnie wywołanie funkcji wirtualnej).

    #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 niczego nie rejestruje.

    Ponieważ Twój predykat jest tak naprawdę stałą logiczną, poniższe elementy szybko obejdą bieżący problem. Zobacz tę 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; }
      };
    }
    
5gon12eder
źródło
4
@TC Zobacz to pytanie, aby poznać szczegóły, dlaczego to działa
Shafik Yaghmour
Zauważ, że ogólnie, jeśli znasz dane przechwytywania w czasie kompilacji, możesz je przekonwertować na dane typu, a następnie wrócisz do lambda bez przechwytywania - zobacz odpowiedź , którą właśnie napisałem na inne pytanie (dzięki @ Odpowiedź 5gon12eder tutaj).
dan-man,
Czy obiekt nie powinien mieć dłuższej żywotności niż funkcja wskaźnika? Chciałbym go użyć do glutReshapeFunc.
ar2015
nie polecam tej sugestii, rzeczy, które działają magicznie, wprowadzają nowe błędy. i praktyki, które towarzyszą tym błędom. jeśli chcesz użyć std :: function, powinieneś zobaczyć wszystkie sposoby, w jakie można użyć std :: function. ponieważ niektóre sposoby mogą być czymś, czego nie chcesz.
TheNegative
1
To nie odpowiada na pytanie. Gdyby ktoś mógł użyć std::functionlub lambda - dlaczego by nie miał? Przynajmniej jest to bardziej czytelna składnia. Zwykle należy używać wskaźnika funkcji do interakcji z bibliotekami C (właściwie z dowolną biblioteką zewnętrzną) i upewnić się, że nie można go zmodyfikować, aby zaakceptować std :: function lub lambda.
Cześć Anioł
40

Wyrażenia lambda, nawet te przechwycone, mogą być obsługiwane jako wskaźnik funkcji (wskaźnik do funkcji składowej).

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;

Aby zbudować klasę, która może zacząć działać jak „std :: function”, najpierw potrzebujesz klasy / struct, niż może przechowywać wskaźnik obiektu i funkcji. Potrzebujesz także operatora (), aby go wykonać:

// 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 uruchamiać przechwycone, nieprzechwycone 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;
}
Noxxer
źródło
Łał, to niesamowite! Więc moglibyśmy użyć wewnętrznych wskaźników klasy lambda (do operatora funkcji członka ()), aby wywołać zapisane lambdy w klasie opakowania !! NIESAMOWITY!! Dlaczego więc potrzebujemy funkcji std ::? I czy można zrobić wyrażenie lambda <decltype (lambda), int, int, int>, aby automatycznie wydedukować / te parametry „int” bezpośrednio z samej przekazanej lambda?
barney
2
Dodałem krótką wersję własnego kodu. powinno to działać z prostym auto f = make :: function (lambda); Ale jestem pewien, że znajdziesz wiele sytuacji, w których mój kod nie będzie działać. std :: function jest o wiele lepiej skonstruowane i powinno być tym, do którego należy się udać podczas pracy. To jest do celów edukacyjnych i użytku osobistego.
Noxxer,
14
To rozwiązanie wymaga wywołania lambdy za pomocą operator()implementacji, więc jeśli dobrze ją czytam, nie sądzę, że zadziałałoby wywołanie lambda za pomocą wskaźnika funkcji w stylu C , prawda? Właśnie o to pytało pierwotne pytanie.
Remy Lebeau,
13
Twierdziłeś, że lambdy można traktować jako wskaźniki funkcji, czego nie zrobiłeś. Stworzyłeś inny obiekt do przechowywania lambdy, który nic nie robi, mógłbyś po prostu użyć oryginalnej lambdy.
Przechodzień Do
9
To nie jest „przekazywanie przechwytywania lambda jako wskaźnika funkcji”. Jest to „przekazywanie przechwytywania lambda jako obiektu zawierającego między innymi wskaźnik funkcji”. Istnieje świat różnic.
n. „zaimki” m.
15

Przechwytywanie lambdas nie może być konwertowane na wskaźniki funkcji, jak wskazano w tej odpowiedzi .

Jednak dostarczenie wskaźnika funkcji do interfejsu API, który akceptuje tylko jeden, jest często dość uciążliwe. Najczęściej cytowaną metodą jest zapewnienie funkcji i wywołanie z nią obiektu statycznego.

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

To jest nudne. Kontynuujemy ten pomysł i automatyzujemy proces tworzenia wrapperi 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 jako

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

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

Relacja na żywo

Zasadniczo deklaruje to anonimową funkcję przy każdym wystąpieniu fnptr.

Zauważ, że wywołania fnptrnadpisują wcześniej napisane callablepodane kalendarze tego samego typu. W pewnym stopniu naprawiamy to za pomocą intparametru N.

std::function<void()> func1, func2;
auto fn1 = fnptr<void(), 1>(func1);
auto fn2 = fnptr<void(), 2>(func2);  // different function
Przechodzień
źródło
wymuszenie deklarowania liczby całkowitej N byłoby eleganckim sposobem zapamiętania klienta, aby uniknąć nadpisania wskaźników funkcji w czasie kompilacji.
fiorentinoing
2

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

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

Używanie Curl jako przykładu ( informacje o debugowaniu 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
źródło
3
Ta lambda nie ma schwytania. Problemem PO jest przechwytywanie, bez konieczności dedukcji typu wskaźnika funkcji (co jest tym, co daje ci +sztuczka).
Sneftel
2

Chociaż podejście wzorcowe jest sprytne z różnych powodów, ważne jest, aby pamiętać o cyklu życia lambda i zarejestrowanych zmiennych. Jeśli ma być użyta jakakolwiek forma wskaźnika lambda, a lambda nie jest kontynuacją w dół, wówczas należy użyć tylko kopiującej [=] lambda. To znaczy, nawet wtedy przechwytywanie wskaźnika do zmiennej na stosie jest NIEBEZPIECZNE, jeśli czas życia tych przechwyconych wskaźników (odwijanie stosu) jest krótszy niż okres eksploatacji lambda.

Prostszym rozwiązaniem do uchwycenia lambda jako wskaźnika jest:

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

na przykład, new std::function<void()>([=]() -> void {...}

Pamiętaj tylko, aby później delete pLamdba, aby nie dopuścić do wycieku pamięci lambda. Tajemnicą do zrozumienia tutaj jest to, że lambdas mogą przechwytywać lambdas (zadaj sobie pytanie, jak to działa), a także, że std::functionaby działać ogólnie, implementacja lambda musi zawierać wystarczające informacje wewnętrzne, aby zapewnić dostęp do wielkości lambda (i przechwyconych) danych ( dlatego deletepowinien działać [uruchamianie niszczycieli przechwyconych typów]).

smallscript
źródło
Po co zawracać sobie newgłowę funkcją - std :: już zapisuje lambda na stercie ORAZ unika konieczności pamiętania wywołania delete.
Chris Dodd
0

Nie bezpośrednia odpowiedź, ale niewielka odmiana użycia wzorca szablonu „funktora”, aby ukryć specyfikę typu lambda i zachować ładny i prosty kod.

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

Podstawowa forma twojej klasy może wyglądać następująco:

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

Gdzie przekazujesz typ funkcji jako część zastosowanego typu klasy:

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

Ponownie nie byłem pewien, dlaczego to robisz x, bardziej sensowne (dla mnie) było posiadanie parametru przekazywanego do lambda), abyś mógł użyć:

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

Zobacz link do pełnego przykładu

pokarm_kodowy
źródło
-2

Jak wspomnieli inni, możesz zastąpić funkcję Lambda zamiast wskaźnika funkcji. Używam tej metody w moim interfejsie C ++ do FKS ODS 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);
Beniekg
źródło