Yakalama lambda'yı fonksiyon işaretçisi olarak geçirme


210

Bir lambda fonksiyonunu fonksiyon işaretçisi olarak geçirmek mümkün müdür? Eğer öyleyse, derleme hatası alıyorum çünkü yanlış bir şey yapmalıyım.

Aşağıdaki örneği düşünün

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

Bu derlemeye çalıştığımda , aşağıdaki derleme hatası alıyorum:

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

Bu sindirmek için bir hata mesajı bir heck, ama ben bunun üzerinden ne elde lambda bir gibi muamele edilemez olduğunu düşünüyorum constexprbu yüzden bir işlev işaretçisi olarak geçemez? Ben de xconst yapmaya çalıştım , ama bu yardımcı gibi görünmüyor.


34
lambda, yalnızca hiçbir şey yakalamazlarsa işaretçi işlevini bozabilir.
Jarod42


Gelecek nesiller için, yukarıdaki bağlantılı blog yazısı artık devblogs.microsoft.com/oldnewthing/20150220-00/?p=44623
warrenm

Yanıtlar:


205

Bir lambda sadece yakalamadığı takdirde bir fonksiyon işaretçisine dönüştürülebilir, taslak C ++ 11 standart bölümünden 5.1.2 [expr.prim.lambda] diyor ( vurgu mayın ):

Lambda yakalaması olmayan bir lambda ifadesi için kapatma türünün, kapatma türünün işlev çağrısı operatörü ile aynı parametreye ve dönüş türlerine sahip işlevlere işaret etmek için genel bir sanal olmayan açık olmayan const dönüştürme işlevi vardır . Bu dönüştürme işlevi tarafından döndürülen değer, çağrıldığında, kapatma türünün işlev çağrısı işlecini çağırmakla aynı etkiye sahip bir işlevin adresi olmalıdır.

Not, cppreference ayrıca Lambda fonksiyonları ile ilgili bölümlerini de kapsar .

Yani aşağıdaki alternatifler işe yarayacaktır:

typedef bool(*DecisionFn)(int);

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

ve böylece:

typedef bool(*DecisionFn)();

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

ve 5gon12eder'in işaret ettiği gibi , kullanabilirsiniz std::function, ancak bunun std::functionağır ağırlık olduğunu unutmayın , bu yüzden maliyetsiz bir değiş tokuş değildir.


2
Yan not: C maddeleri tarafından kullanılan yaygın bir çözüm void*, a'yı tek parametre olarak geçirmektir . Normalde "kullanıcı işaretçisi" olarak adlandırılır. Aynı zamanda nispeten hafif, ancak mallocbiraz yer kaplamanızı gerektiriyor .
Monica'nın Davası

94

Shafik Yaghmour'un cevabı, lambda'nın bir yakalama varsa neden bir işlev işaretçisi olarak geçilemeyeceğini doğru bir şekilde açıklıyor. Sorun için iki basit düzeltme göstermek istiyorum.

  1. std::functionHam işlev işaretçileri yerine kullanın .

    Bu çok temiz bir çözüm. Bununla birlikte, tür silme için bazı ek yükler içerdiğini unutmayın (muhtemelen bir sanal işlev çağrısı).

    #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. Hiçbir şey yakalamayan bir lambda ifadesi kullanın.

    Tahmininiz gerçekten sadece bir boole sabiti olduğundan, aşağıdakiler hızlı bir şekilde mevcut sorun etrafında çözülecektir. Bunun neden ve nasıl çalıştığına dair iyi bir açıklama için bu cevaba bakınız .

    // Your 'Decide' class as in your post.
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree {
        (x > 3) ? [](){ return true; } : [](){ return false; }
      };
    }

4
@TC Neden çalıştığını öğrenmek için bu soruya bakın
Shafik Yaghmour

Genel olarak, derleme zamanında yakalama verilerini biliyorsanız, verileri veri türüne dönüştürebileceğinizi ve daha sonra yakalama olmadan bir lambdaya geri döndüğünüzü unutmayın - başka bir soruya yazdığım bu cevaba bakın (@ 5gon12eder yanıtı).
dan-man

Nesnenin işaretçi işlevinden daha uzun bir ömrü olmamalı mı? Ben kullanmak istiyorum glutReshapeFunc.
ar2015

Bu öneriyi tavsiye etmiyorum, sihirli çalışma eğilimi olan şeyler yeni hatalar getiriyor. ve bu hatalarla birlikte gelen uygulamalar. std :: fonksiyonunu kullanmak istiyorsanız, std :: fonksiyonunun kullanılabileceği her türlü yolu görmelisiniz. çünkü bazı yollar belki istemediğiniz bir şey.
TheNegative

1
Bu soruya cevap vermiyor. Biri std::functionveya lambda kullanabilirse - neden olmasın ki? En azından daha okunabilir bir sözdizimi. Genellikle, C kütüphaneleriyle (aslında herhangi bir harici kütüphaneyle) etkileşim kurmak için bir işlev işaretçisi kullanmak gerekir ve bir std :: işlevini veya lambda'yı kabul etmek için değiştiremezsiniz.
Hi-Angel

40

Lambda ifadeleri, hatta yakalananlar bile bir işlev işaretçisi (işaretçi-üye işlevi) olarak ele alınabilir.

Zor çünkü lambda ifadesi basit bir işlev değil. Aslında bir işleç () içeren bir nesnedir.

Yaratıcı olduğunuzda bunu kullanabilirsiniz! Std :: function tarzında bir "function" sınıfı düşünün. Nesneyi kaydederseniz işlev işaretçisini de kullanabilirsiniz.

İşlev işaretçisini kullanmak için aşağıdakileri kullanabilirsiniz:

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;

"Std :: function" gibi çalışmaya başlayabilecek bir sınıf oluşturmak için, önce nesne ve işlev işaretçisini depolayabileceğiniz bir sınıfa / yapıya ihtiyacınız vardır. Ayrıca çalıştırmak için bir operatöre () ihtiyacınız vardır:

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

Bununla artık tıpkı orijinali kullandığınız gibi yakalanmış, yakalanmamış lambdaları çalıştırabilirsiniz:

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

Bu kod VS2015 ile çalışır

04.07.17 Güncellemesi:

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

Vay canına bu inanılmaz! Bu yüzden sadece bir sarmalayıcı sınıfında depolanan lambdaları çağırmak için lambda'nın sınıf iç göstergelerini (üye fonksiyon operatörüne ()) kullanabiliriz! İNANILMAZ!! Neden hiç std :: fonksiyonuna ihtiyacımız var? Ve lambda_expression <decltype (lambda), int, int, int> bu "int" parametreleri doğrudan geçirilen lambda'nın kendisinden otomatik olarak çıkarmak için mümkün mü?
barney

2
Kendi kodumun kısa bir versiyonunu ekledim. bunun basit bir otomatik f = make :: function (lambda) ile çalışması gerekir; Ama kodum işe yaramaz durum bol bulacaksınız oldukça eminim. std :: işlevi bundan çok daha iyi yapılandırılmıştır ve çalışırken gitmeniz gerekir. Bu, eğitim ve kişisel kullanım içindir.
Noxxer

14
Bu çözüm lambda'yı bir operator()uygulama aracılığıyla çağırmayı içerir , bu yüzden doğru okursam lambda'yı C stili fonksiyon işaretçisi kullanarak çağırmanın işe yarayacağını düşünmüyorum , değil mi? Asıl soru budur.
Remy Lebeau

13
Lambdaların, yapmadığınız işlev işaretçileri olarak ele alınabileceğini iddia ettiniz. Bir lambda tutmak için başka bir nesne yarattınız, bu hiçbir şey yapmaz, sadece orijinal lambda'yı kullanabilirsiniz.
Passer By

9
Bu "lambda'yı fonksiyon göstericisi olarak ele geçirmek" değildir. Bu "diğer şeylerin yanı sıra fonksiyon işaretçisi içeren bir nesne olarak lambda'yı ele geçirme" dir. Bir fark dünyası var.
n. 'zamirler' m.

15

Bu cevabın işaret ettiği gibi lambdaları ele geçirmek fonksiyon göstericilerine dönüştürülemez .

Bununla birlikte, bir API'ye yalnızca bir tanesini kabul eden bir işlev işaretçisi sağlamak genellikle bir acıdır. Bunu yapmak için en sık atıfta bulunulan yöntem, bir işlev sağlamak ve onunla statik bir nesne çağırmaktır.

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

Bu sıkıcı. Bu fikri daha ileri götürüyoruz wrapperve hayatı yaratma ve otomatik hale getirme sürecini otomatikleştiriyoruz .

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

Ve şu şekilde kullanın

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

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

Canlı

Bu aslında her oluşumunda anonim bir işlev bildirir fnptr.

Aynı türden fnptrdaha önce yazılmış callableverilen callables'ın üzerine yazma çağrılarının üzerine yazıldığını unutmayın . Bunu bir dereceye kadar intparametre ile düzeltiriz N.

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

N tamsayısının bildirilmeye zorlanması, istemcinin derleme zamanında işlev işaretlerinin üzerine yazılmasını önlemek için hatırlamanın zarif bir yolu olacaktır.
fiorentinoing

2

C işlev işaretçisi olarak bir lambda kullanmak için bir kısayol şudur:

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

Curl'u örnek olarak kullanma ( curl hata ayıklama bilgisi )

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

3
Bu lambda'nın yakalanması yok. OP'nin sorunu, işlev işaretçisi türünü ( +hile sizi alır) çıkarmak zorunda kalmadan yakalamadır .
Sneftel

2

Şablon yaklaşımı çeşitli nedenlerden ötürü akıllı olsa da, lambda ve yakalanan değişkenlerin yaşam döngüsünü hatırlamak önemlidir. Herhangi bir lambda işaretçisi kullanılacaksa ve lambda aşağı doğru devam etmiyorsa, sadece bir kopyalama [=] lambda kullanılmalıdır. Yani, o zaman bile, yakalanan bu işaretçilerin (yığın çözme) ömrü lambda'nın ömründen daha kısaysa, yığın üzerindeki bir değişkene bir işaretçi yakalamak UNSAFE olur.

Bir lambda'yı işaretçi olarak yakalamak için daha basit bir çözüm:

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

Örneğin, new std::function<void()>([=]() -> void {...}

Daha sonra hatırlamayı unutmayın, delete pLamdbabu yüzden lambda hafızasını sızdırmadığınızdan emin olun. Burada fark edilmesi gereken sır, lambdaların lambdaları yakalayabileceğidir (kendinize bunun nasıl çalıştığını sorun) ve ayrıca std::functionlambda uygulamasının lambda (ve yakalanan) boyutuna erişim sağlamak için yeterli dahili bilgi içermesi gerektiğidir ( işte bu yüzden delete[yakalanan türlerin yıkıcılarını çalıştırmak] gerekir.


Neden new- std :: işleviyle uğraşmak lambda'yı yığın üzerinde zaten saklıyor VE silme çağrısını hatırlamak zorunda kalmıyor.
Chris Dodd

0

Doğrudan bir cevap değil, ama lambda türünün özelliklerini gizlemek için "functor" şablon desenini kullanmak için küçük bir varyasyon ve kodu güzel ve basit tutar.

Karar sınıfını nasıl kullanmak istediğinizden emin değildim, bu yüzden sınıfı kullanan bir işlevle genişletmek zorunda kaldım. Tüm örneği buradan görebilirsiniz: https://godbolt.org/z/jtByqE

Sınıfınızın temel formu şöyle görünebilir:

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

Burada kullanılan sınıf türünün bir parçası olarak işlevin türünü ilettiğiniz yer:

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

Yine, neden yakalayabileceğinizden emin değildim x(bana göre) lambda'ya geçtiğiniz bir parametreye sahip olmak), böylece şöyle kullanabilirsiniz:

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

Tam bir örnek için bağlantıya bakın


-2

Diğerleri tarafından belirtildiği gibi, fonksiyon işaretçisi yerine Lambda işlevini kullanabilirsiniz. Bu yöntemi F77 ODE çözücü RKSUITE için C ++ arabirimimde kullanıyorum.

//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);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.