2013-07-15 22 views
16

chciałbym zrobić coś takiego:Przełęcz lambda wyrażenie argumentem lambda C++ 11

int main() 
{ 
    auto f = [/*some variables*/](/*take lambda function*/) 
    {/*something with lambda function*/}; 

    f([/*other variables*/](/*variables to be decided by f()*/) 
    {/*something with variables*/}); 
} 

Wiem, że jest możliwe, aby przejść do funkcji lambda, a także do lambda. następujące utwory:

int main() 
{ 
    int x=0; 
    int y=0; 
    auto f = [x,y](double (func)(int)) -> double 
    {func(0); return 0.0;}; 

    f([](int i) -> double 
    {return 0.0;}); 
} 

Ale dodaje nie działa (jak najszybciej zmienić zmienne zakresu na dodatek [x])

int main() 
{ 
    int x=0; 
    int y=0; 
    auto f = [x,y](double (func)(int)) -> double 
    {func(0); return 0.0;} 

    f([x](int i) -> double //[x] does not work 
    {return 0.0;}); 
} 

co daje błąd:

error: function "lambda [](double (*)(int))->double::operator()" cannot be called with the given argument list 
     argument types are: (lambda [](int)->double) 
     object type is: lambda [](double (*)(int))->double 

czy ktoś ma pomysł, jak to naprawić lub obejść? Używam kompilatora Intel ICPC (ICC) 13.1.2 z std = C++ 11

Dzięki

+13

Przechwytywanie lambdas nie są zamienne do wskaźników funkcji –

+0

dlaczego nie "constexpr"? 'auto' jest tak czy inaczej funkcją kompilacji ... – user2485710

+4

@AndyProwl What? Brak szczegółowej odpowiedzi od Ciebie w ciągu 10 minut? Daj spokój, sprawiasz, że pracujemy tutaj zbyt ciężko, a ty nawet nie przepadłeś za dnia ;-) – TemplateRex

Odpowiedz

30

Jest kilka rzeczy do wyjaśnienia w odniesieniu do pytania. Pierwszym z nich jest czym jest lambda?

Wyrażenie lambda prosty ekspresji, z którego kompilator generuje unikalny rodzaj, który nie może być nazwany i jednocześnie generuje wystąpienie typu. Kiedy piszesz: [](int i) { std::cout << i; } kompilator wygeneruje dla ciebie typ, który jest w przybliżeniu:

struct __lambda_unique_name { 
    void operator()(int i) const { std::cout << i; } 
}; 

Jak widać, jest to nie funkcją, ale to typ, który implementuje operator() jako funkcja const członkowskim. Jeśli lambda zrobiła jakiekolwiek przechwytywanie, kompilator wygenerowałby kod do przechwytywania wartości/referencji.

Jako narożna obudowa, dla lambdas takich jak powyższe, gdzie nie ma przechwyconego stanu, język pozwala na konwersję z typu lambda na wskaźnik działający z podpisem operator() (minus część this) , więc lambda powyżej można niejawnie konwertowane do wskaźnika funkcjonować biorąc int i powrocie nic:

void (*f)(int) = [](int i) { std::cout << i; } 

Skoro podstawy zostały określone w kodzie masz ten lambda:

auto f = [x,y](double (func)(int)) -> double {func(0); return 0.0;}; 

Reguły dla parametrów do funkcji (które również dotyczą lambdas) określają, że argument nie może być typu , funkcja, więc argument do lambda rozpada się na wskaźnik do funkcji (w ten sam sposób, co argument tablicy typów rozpady do typu wskaźnika):

auto f = [x,y](double (*func)(int)) -> double {func(0); return 0.0;}; 

W późniejszym momencie próbujesz przekazać lambdę, która przechwytuje jako argument. Ponieważ istnieje przechwytywanie, specjalna reguła nie ma zastosowania, a lambda nie jest wymienna na wskaźnik funkcji, który powoduje pojawienie się błędu kompilatora.

W obecnym standardzie można przejść na jeden z dwóch sposobów. Można użyć typu skasowaniu usunąć dokładny typ na żądanie podmiotu z podpisem:

auto f = [x,y](std::function<double(int)> func) -> double {func(0); return 0.0;}; 

Ponieważ std::function<double(int)> można zainicjować z dowolnego na żądanie podmiotu z odpowiednim podpisem, to zaakceptuje lambdy w kod poniżej, kosztem usunięcia typu, który zwykle oznacza dynamiczną alokację i dynamiczną wysyłkę.

Alternatywnie możesz upuścić cukier syntaktyczny i rzucić pierwszy równoważnik lambda ręcznie, ale spraw, aby był ogólny. W tym przypadku, gdzie lambda jest prosty to może być ważna opcja:

struct mylambda { 
    template <typename F> 
    double operator()(F fn) const { 
     fn(0); return 0.0; 
    } 
} f; 
// then use the non-lambda as you tried: 
f([x](int i) -> double {return 0.0;}); 

Wreszcie, jeśli jesteś wystarczająco cierpliwy, można czekać na C++ 14, gdzie (najprawdopodobniej, że nie został jeszcze ratyfikowana) nie będzie wsparcie dla polimorficznych lambdas który uprości tworzenie powyższej klasy:

auto f = [](auto fn) { fn(0.0); return 0.0; } // unrolls to 'mylambda' above 
+1

Z tego, co przeczytałem, a także z [tego komentarza] (http: // /stackoverflow.com/questions/7951377/what-is-the-type-of-lambda-when-deduced-w-auto-in-c11#comment14872356_8050769) 'std :: function' jest wolniejszym mechanizmem niż technika szablonowa Pokazujesz, chociaż niektórzy twierdzą, że jest to spowodowane połączeniem wirtualnym, podczas gdy mówisz, że jest to spowodowane alokacją dynamiczną (chociaż mówisz również o dynamicznej wysyłce, która jest równoznaczna z 'wirtualnym'). szablon? – johnbakers

5

Spróbuj użyć std :: funkcję:

#include <functional> 
int main() 
{ 
    int x=0; 
    int y=0; 
    auto f = [x,y](std::function<double(int)> func) -> double 
      {func(0); return 0.0;}; 

    f([x](int i) -> double {return 0.0;}); 
} 
+0

Dzięki za szybką odpowiedź Próbowałem używać tego, ale kompilator intel nie obsługuje go, jak pokazano [tutaj] (http://stackoverflow.com/questions/6527064/stdfunction-and-the-intel-compiler-version-11-1). Biblioteka boost/function może być potencjalną alternatywą, ale chciałbym spróbować uniknąć jej importowania.Co może być alternatywą? – Couchy311

+1

Uaktualnij/przełącz kompilator. Nie można po prostu przechwycić zmiennych w surowe wskaźniki funkcyjne, potrzebny jest do tego obiekt. Jeśli twój kompilator dostarcza lambdy, ale nie ma możliwości ich zapisania (co jest głównym celem std :: function), IMHO nie masz naprawdę wsparcia dla lambdas w swoim kompilatorze. – DanielKO

+0

hmmm ... prawda, prawdopodobnie powinienem znaleźć sposób obejścia lambdas: ( – Couchy311

1

można spróbować coś takiego następujące jeśli znasz typ lambda wcześniej, na przykład:

int main() 
{ 
    int x = 0, y = 0; 

    auto f = [x](int i)->double { 
     return (double)x; 
    }; 

    auto f2 = [x,y](decltype(f) func)->double { 
     return func(0); 
    }; 

    f2(f); 

    return 0; 
} 

lub alternatywne można użyć biblioteki <functional> fo ra rozwiązanie bardziej ogólne, na przykład:

auto f = [x,y](std::function<double(int)> func) { /* Do stuff */ }; 
3

Być może trzeba po prostu zacisnąć zęby i realizować własne funktory jak my w ciemnych wieków:

struct F { 
    int x; 
    int y; 

    F(int x_, int y_) : x(x_), y(y_) {} 

    template <typename G> 
    double operator() (G&& g) const { 
     g(0); 
     return 0.0; 
    } 
}; 

#include <iostream> 

int main() 
{ 
    int x = 0; 
    int y = 0; 
    auto f = F(x, y); 

    f([x](int i){return 0.0;}); 
    f([](int i){std::cout << i << std::endl;}); 
} 

To powinno utrzymać się dzieje, dopóki kompilator obsługuje ogólne lambdy C++ 14.

+0

Jaki jest cel lub korzyść z używania tam wartości odniesienia rvalue? (Tzn. Jak jest '(G && g)' lepsze niż '(G g)' ? –

+1

@ChrisBecke Akceptowanie parametru przez przekazanie odwołania bez przekazywania jest idiomatyczne dla "Chcę ewentualnie zmutować ten argument i być niepomny na kategorię wartości". – Casey

0

można cify lambda przechwytywania, ale to rozwiązanie ma swoje ograniczenia:

#include <new> 

#include <utility> 

namespace 
{ 

template <typename F, int I, typename L, typename R, typename ...A> 
inline F cify(L&& l, R (*)(A...) noexcept(noexcept(
    std::declval<F>()(std::declval<A>()...)))) 
{ 
    static L l_(std::forward<L>(l)); 
    static bool full; 

    if (full) 
    { 
    l_.~L(); 

    new (static_cast<void*>(&l_)) L(std::forward<L>(l)); 
    } 
    else 
    { 
    full = true; 
    } 

    return [](A... args) noexcept(noexcept(
     std::declval<F>()(std::forward<A>(args)...))) -> R 
    { 
     return l_(std::forward<A>(args)...); 
    }; 
} 

} 

template <typename F, int I = 0, typename L> 
inline F cify(L&& l) 
{ 
    return cify<F, I>(std::forward<L>(l), F()); 
} 


int main() 
{ 
    int x=0; 
    int y=0; 
    auto f = [x,y](double (func)(int)) -> double 
    {func(0); return 0.0;}; 

    f(cify<double(*)(int i)>([x](int i) -> double //works now 
    {return 0.0;})); 
} 

Click dla przykładu roboczego.