İşlev işaretçisi olarak yakalamalara sahip C ++ lambda


94

C ++ lambdas ve bunların işlev işaretçilerine örtük dönüşümleriyle oynuyordum. Başlangıç ​​örneğim onları ftw işlevi için geri arama olarak kullanıyordu. Bu beklendiği gibi çalışıyor.

#include <ftw.h>
#include <iostream>

using namespace std;

int main()
{
    auto callback = [](const char *fpath, const struct stat *sb,
        int typeflag) -> int {
        cout << fpath << endl;
        return 0;
    };

    int ret = ftw("/etc", callback, 1);

    return ret;
}

Yakalamaları kullanmak için değiştirdikten sonra:

int main()
{

    vector<string> entries;

    auto callback = [&](const char *fpath, const struct stat *sb,
        int typeflag) -> int {
        entries.push_back(fpath);
        return 0;
    };

    int ret = ftw("/etc", callback, 1);

    for (auto entry : entries ) {
        cout << entry << endl;
    }

    return ret;
}

Derleyici hatasını aldım:

error: cannot convert ‘main()::<lambda(const char*, const stat*, int)>’ to ‘__ftw_func_t {aka int (*)(const char*, const stat*, int)}’ for argument ‘2’ to ‘int ftw(const char*, __ftw_func_t, int)

Biraz okuduktan sonra. Yakalamalar kullanan lambdaların örtük olarak işlev işaretçilerine dönüştürülemeyeceğini öğrendim .

Bunun için bir çözüm var mı? "Örtük olarak" dönüştürülemeyecekleri gerçeği, "açıkça" dönüştürülebilecekleri anlamına mı geliyor? (Başarılı olmadan oyuncu seçmeyi denedim). Lambdas kullanarak girişleri bir nesneye ekleyebilmem için çalışma örneğini değiştirmenin temiz bir yolu nedir ?.


Hangi derleyiciyi kullanıyorsunuz? VS10 mu?
Ramon Zarazua B.

gcc sürüm 4.6.1 20110801 [gcc-4_6-branch revizyon 177033] (SUSE Linux)
duncan

4
Genellikle, durumu geri aramalara geçirmenin C yolu, geri aramaya (genellikle türden void *) ekstra bir bağımsız değişken aracılığıyla yapılır . Kullandığınız kütüphane bu ekstra argümana izin veriyorsa, bir çözüm bulacaksınız. Aksi takdirde, yapmak istediğiniz şeyi temiz bir şekilde başarmanın bir yolu yoktur.
Alexandre C.

Evet. Ftw.h ve nftw.h'nin API'sinin hatalı olduğunu fark ediyorum. Fts.h'yi deneyeceğim
duncan

1
Harika! /usr/include/fts.h:41:3: hata: #error "<fts.h> -D_FILE_OFFSET_BITS == 64" ile kullanılamaz
duncan

Yanıtlar:


49

Lambdaların yakalanması bir durumu korumaya ihtiyaç duyduğundan, bunlar sadece sıradan işlevler olmadıkları için gerçekten basit bir "geçici çözüm" yoktur . Bir işlev göstericisinin amacı, tek bir global işlevi göstermesidir ve bu bilginin bir duruma yer olmamasıdır.

En yakın geçici çözüm (esasen durumsallığı atan), lambda / işlevinizden erişilen bir tür genel değişken sağlamaktır. Örneğin, geleneksel bir functor nesnesi yapabilir ve ona bazı benzersiz (global / statik) örneklere atıfta bulunan statik bir üye işlevi verebilirsiniz.

Ama bu, lambdaları yakalama amacının tamamını ortadan kaldırmak gibi.


3
Daha temiz bir çözüm, işlev göstericisinin bir bağlam parametresine sahip olduğunu varsayarak lambdayı bir bağdaştırıcının içine sarmaktır.
Raymond Chen

4
@RaymondChen: Eğer fonksiyonun nasıl kullanılacağını tanımlama özgürsünüz, o zaman evet, bu bir seçenek. Yine de bu durumda, parametreyi lambda'nın kendisinin bir argümanı haline getirmek daha da kolay olurdu!
Kerrek SB

3
@KerrekSB, global değişkenleri a'ya koyup namespaceonları işaretleyin thread_local, ftwbenzer bir şeyi çözmek için seçtiğim yaklaşım bu.
Kjell Hedström

"bir işlev işaretçisi tek bir genel işlevi gösterir ve bu bilginin bir duruma yer yoktur." -> Java gibi diller bunu nasıl başarabilir? Eh, tabii ki, bu tek, küresel fonksiyon oluşturulur çünkü zamanında ve gömer (daha doğrusu devlet referansı kendi kodunda buna). Yani olan bütün mesele - orada olmalı değil bir olmak tek global fonksiyon ancak birden küresel fonksiyonları - Her zaman lambda için bir çalışma zamanı kullanılır. C ++ 'da bunu yapan gerçekten HİÇBİR ŞEY var mı? (Std :: function tam olarak bu tek amaç için yapıldığını düşünmüştüm)
Dexter

1
@Dexter: errr .. kısa cevap hayır, uzun cevap operatörü aşırı yüklemeyi içeriyor. Ne olursa olsun, benim amacım geçerli. Java, C ++ ile aynı olmayan farklı bir dildir; Java'nın işaretçileri (veya aşırı yüklenebilir çağrı operatörleri) yoktur ve karşılaştırma iyi çalışmaz.
Kerrek SB

47

Az önce bu problemle karşılaştım.

Kod, lambda yakalamaları olmadan iyi derler, ancak lambda yakalamada bir tür dönüştürme hatası vardır.

C ++ 11 ile std::functionçözüm kullanmaktır (düzenleme: işlev imzasının değiştirilmesini gerektirmeyen başka bir çözüm bu örnekten sonra gösterilmektedir). Ayrıca boost::function(aslında önemli ölçüde daha hızlı çalışan) kullanabilirsiniz. Örnek kod - derlenecek şekilde değiştirildi, şununla derlendi gcc 4.7.1:

#include <iostream>
#include <vector>
#include <functional>

using namespace std;

int ftw(const char *fpath, std::function<int (const char *path)> callback) {
  return callback(fpath);
}

int main()
{
  vector<string> entries;

  std::function<int (const char *fpath)> callback = [&](const char *fpath) -> int {
    entries.push_back(fpath);
    return 0;
  };

  int ret = ftw("/etc", callback);

  for (auto entry : entries ) {
    cout << entry << endl;
  }

  return ret;
}

Düzenleme: Orijinal işlev imzasını değiştiremediğim, ancak yine de lambdaları kullanmam gereken eski koda rastladığımda bunu tekrar gözden geçirmek zorunda kaldım. Orijinal işlevin işlev imzasını değiştirmeyi gerektirmeyen bir çözüm aşağıdadır:

#include <iostream>
#include <vector>
#include <functional>

using namespace std;

// Original ftw function taking raw function pointer that cannot be modified
int ftw(const char *fpath, int(*callback)(const char *path)) {
  return callback(fpath);
}

static std::function<int(const char*path)> ftw_callback_function;

static int ftw_callback_helper(const char *path) {
  return ftw_callback_function(path);
}

// ftw overload accepting lambda function
static int ftw(const char *fpath, std::function<int(const char *path)> callback) {
  ftw_callback_function = callback;
  return ftw(fpath, ftw_callback_helper);
}

int main() {
  vector<string> entries;

  std::function<int (const char *fpath)> callback = [&](const char *fpath) -> int {
    entries.push_back(fpath);
    return 0;
  };
  int ret = ftw("/etc", callback);

  for (auto entry : entries ) {
    cout << entry << endl;
  }

  return ret;
}

73
Hayır, bu kabul edilen cevap olmamalı. Nokta, bir işlev işaretçisi yerine ftwalmak için std::function
değişmiyor

Bu cevapta önerilen ikinci çözüm, orijinal imzayı koruyarak @ gregory-pakosz'un endişesine hitap ediyor, ancak yine de harika değil çünkü küresel durumu ortaya koyuyor. Eğer ftwbir boşluk * userdata tartışmıştık, o zaman evgeny-Karpov @ cevabını tercih ediyorum.
gurur

@prideout kabul etti - Küresel durumu da sevmiyorum. Ne yazık ki, ftw'nin imzasının değiştirilemeyeceğini varsayarsak ve void * kullanıcı verisine sahip olmadığı göz önüne alındığında, durum bir yerde saklanmalıdır. 3. parti kitaplığı kullanarak bu problemle karşılaştım. Bu, kütüphane geri aramayı yakalamadığı ve daha sonra kullanmadığı sürece iyi çalışacaktır, bu durumda global değişken, çağrı yığınında sadece fazladan bir parametre gibi davranır. Ftw'nin imzası değiştirilebilirse, void * userdata yerine std :: function kullanmayı tercih ederim.
Jay West

1
bu son derece karmaşık ve kullanışlı bir çözüm, @Gregory size "işe yarıyor" demeliyim.
fiorentinoing

17

ORİJİNAL

Lambda işlevleri çok kullanışlıdır ve bir kodu azaltır. Benim durumumda paralel programlama için lambdalara ihtiyacım vardı. Ancak yakalama ve işlev işaretçileri gerektirir. Benim çözümüm burada. Ancak yakaladığınız değişkenlerin kapsamına dikkat edin.

template<typename Tret, typename T>
Tret lambda_ptr_exec(T* v) {
    return (Tret) (*v)();
}

template<typename Tret = void, typename Tfp = Tret(*)(void*), typename T>
Tfp lambda_ptr(T& v) {
    return (Tfp) lambda_ptr_exec<Tret, T>;
}

Misal

int a = 100;
auto b = [&]() { a += 1;};
void (*fp)(void*) = lambda_ptr(b);
fp(&b);

Dönüş değeri olan örnek

int a = 100;
auto b = [&]() {return a;};
int (*fp)(void*) = lambda_ptr<int>(b);
fp(&b);

GÜNCELLEME

Gelişmiş versiyon

Bir işlev işaretçisi olarak yakalamalar içeren C ++ lambda hakkında ilk gönderinin yayınlanmasından bu yana bir süredir. Ben ve diğer insanlar için kullanılabilir olduğu için bazı iyileştirmeler yaptım.

Standart işlev C işaretçi api, void fn (void * data) kuralını kullanır. Varsayılan olarak bu kural kullanılır ve lambda bir void * bağımsız değişkeni ile bildirilmelidir.

Geliştirilmiş uygulama

struct Lambda {
    template<typename Tret, typename T>
    static Tret lambda_ptr_exec(void* data) {
        return (Tret) (*(T*)fn<T>())(data);
    }

    template<typename Tret = void, typename Tfp = Tret(*)(void*), typename T>
    static Tfp ptr(T& t) {
        fn<T>(&t);
        return (Tfp) lambda_ptr_exec<Tret, T>;
    }

    template<typename T>
    static void* fn(void* new_fn = nullptr) {
        static void* fn;
        if (new_fn != nullptr)
            fn = new_fn;
        return fn;
    }
};

Örnek

int a = 100;
auto b = [&](void*) {return ++a;};

Yakalamalarla lambda'yı bir C işaretçisine dönüştürme

void (*f1)(void*) = Lambda::ptr(b);
f1(nullptr);
printf("%d\n", a);  // 101 

Bu şekilde de kullanılabilir

auto f2 = Lambda::ptr(b);
f2(nullptr);
printf("%d\n", a); // 102

Dönüş değeri kullanılması durumunda

int (*f3)(void*) = Lambda::ptr<int>(b);
printf("%d\n", f3(nullptr)); // 103

Ve verilerin kullanılması durumunda

auto b2 = [&](void* data) {return *(int*)(data) + a;};
int (*f4)(void*) = Lambda::ptr<int>(b2);
int data = 5;
printf("%d\n", f4(&data)); // 108

3
Bu kesinlikle bir lambdayı C tarzı bir işlev işaretçisine dönüştürmek için gördüğüm en uygun çözüm. Bunu bir argüman olarak alan fonksiyonun durumunu temsil eden fazladan bir parametreye ihtiyaç duyacaktır, bu parametre genellikle C kütüphanelerinde "void * user" olarak adlandırılır, böylece onu çağırırken onu fonksiyon göstericisine geçirebilir.
Codoscope

10

Yerel olarak global (statik) yöntem kullanılarak aşağıdaki şekilde yapılabilir

template <class F>
auto cify_no_args(F&& f) {
  static F fn = std::forward<F>(f);
  return [] {
    return fn();
  };
}

Varsayalım ki bizde

void some_c_func(void (*callback)());

Yani kullanım olacak

some_c_func(cify_no_args([&] {
  // code
}));

Bu işe yarar çünkü her lambda benzersiz bir imzaya sahiptir, bu nedenle statik hale getirmek bir sorun değildir. Aşağıda, değişken sayıda bağımsız değişkene ve aynı yöntemi kullanan herhangi bir dönüş türüne sahip genel bir sarmalayıcı verilmiştir.

template <class F>
struct lambda_traits : lambda_traits<decltype(&F::operator())>
{ };

template <typename F, typename R, typename... Args>
struct lambda_traits<R(F::*)(Args...)> : lambda_traits<R(F::*)(Args...) const>
{ };

template <class F, class R, class... Args>
struct lambda_traits<R(F::*)(Args...) const> {
    using pointer = typename std::add_pointer<R(Args...)>::type;

    static pointer cify(F&& f) {
        static F fn = std::forward<F>(f);
        return [](Args... args) {
            return fn(std::forward<Args>(args)...);
        };
    }
};

template <class F>
inline lambda_traits<F>::pointer cify(F&& f) {
    return lambda_traits<F>::cify(std::forward<F>(f));
}

Ve benzer kullanım

void some_c_func(int (*callback)(some_struct*, float));

some_c_func(cify([&](some_struct* s, float f) {
    // making use of "s" and "f"
    return 0;
}));

1
bunun kapatmayı (ptr alırken) + değiştirgeleri (arama sırasında) kopyalayacağını unutmayın. Aksi takdirde, zarif bir çözüm
Ivan Sanz-Carasa

yalnızca başlık yardımcı kitaplığı: gist.github.com/isc30/fab67e5956fe8f2097bed84ebc42c1e8
Ivan Sanz-

1
@ IvanSanz-Carasa İşaret ettiğiniz için teşekkürler. Kapanış türleri CopyAssignable değildir, ancak işlevciler vardır. Yani haklısın, burada mükemmel yönlendirmeyi kullanmak daha iyidir. Öte yandan argümanlar için düz C evrensel referansları desteklemediği için pek bir şey yapamayız, ancak en azından değerleri lambda'mıza geri iletebiliriz. Bu fazladan bir kopya kaydedebilir. Kodu düzenledi.
Vladimir Talybin

@RiaD Evet, çünkü burada lambda statik bir örnek oluşturacak, bunun yerine referans ile yakalamanız gerekecek, örneğin for- loop'unuzda =kullanmak yerine &i.
Vladimir Talybin

5

Hehe - oldukça eski bir soru, ama yine de ...

#include <iostream>
#include <vector>
#include <functional>

using namespace std;

// We dont try to outsmart the compiler...
template<typename T>
int ftw(const char *fpath, T callback) {
  return callback(fpath);
}

int main()
{
  vector<string> entries;

  // ... now the @ftw can accept lambda
  int ret = ftw("/etc", [&](const char *fpath) -> int {
    entries.push_back(fpath);
    return 0;
  });

  // ... and function object too 
  struct _ {
    static int lambda(vector<string>& entries, const char* fpath) {
      entries.push_back(fpath);
      return 0;
    }
  };
  ret = ftw("/tmp", bind(_::lambda, ref(entries), placeholders::_1));

  for (auto entry : entries ) {
    cout << entry << endl;
  }

  return ret;
}

0

Yakalama lambdasını bir işlev işaretçisine dönüştürmenin hileli bir yolu vardır, ancak onu kullanırken dikkatli olmanız gerekir:

/codereview/79612/c-ifying-a-capturing-lambda

Kodunuz daha sonra şöyle görünecektir (uyarı: beyin derlemesi):

int main()
{

    vector<string> entries;

    auto const callback = cify<int(*)(const char *, const struct stat*,
        int)>([&](const char *fpath, const struct stat *sb,
        int typeflag) -> int {
        entries.push_back(fpath);
        return 0;
    });

    int ret = ftw("/etc", callback, 1);

    for (auto entry : entries ) {
        cout << entry << endl;
    }

    return ret;
}

0

Çözümüm, statik bir lambda'ya başvurmak için sadece bir işlev göstericisi kullanın.

typedef int (* MYPROC)(int);

void fun(MYPROC m)
{
    cout << m(100) << endl;
}

template<class T>
void fun2(T f)
{
    cout << f(100) << endl;
}

void useLambdaAsFunPtr()
{
    int p = 7;
    auto f = [p](int a)->int {return a * p; };

    //fun(f);//error
    fun2(f);
}

void useLambdaAsFunPtr2()
{
    int p = 7;
    static auto f = [p](int a)->int {return a * p; };
    MYPROC ff = [](int i)->int { return f(i); };
    //here, it works!
    fun(ff);
}

void test()
{
    useLambdaAsFunPtr2();
}

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.