2013-08-25 20 views
67

Bawiąc się z Lambdą, znalazłem interesujące zachowanie, którego nie w pełni rozumiem.Lambda funkcjonuje jako klasa podstawowa

Dodatek Mam struct Overload, który pochodzi z 2 parametrów szablonu i ma klauzulę using F1::operator();.

Teraz jeśli wywodzą się z dwóch funktorów mogę uzyskać dostęp tylko operator() F1 (jak bym się spodziewał)

Gdybym wywodzą się z dwóch funkcji lambda nie jest to już prawdą mogę uzyskać dostęp do operatora () również z F2.

#include <iostream> 

// I compiled with g++ (GCC) 4.7.2 20121109 (Red Hat 4.7.2-8) 
// 
// g++ -Wall -std=c++11 -g main.cc 
// g++ -Wall -std=c++11 -DFUNCTOR -g main.cc 
// 
// or clang clang version 3.3 (tags/RELEASE_33/rc2) 
// 
// clang++ -Wall -std=c++11 -g main.cc 
// clang++ -Wall -std=c++11 -DFUNCTOR -g main.cc 
// 
// on a Linux localhost.localdomain 3.9.6-200.fc18.i686 #1 SMP Thu Jun 13 
// 19:29:40 UTC 2013 i686 i686 i386 GNU/Linux box 


struct Functor1 
{ 
    void operator()() { std::cout << "Functor1::operator()()\n"; } 
}; 

struct Functor2 
{ 
    void operator()(int) { std::cout << "Functor2::operator()(int)\n"; } 
}; 

template <typename F1, typename F2> 
struct Overload : public F1, public F2 
{ 
    Overload() 
     : F1() 
     , F2() {} 

    Overload(F1 x1, F2 x2) 
     : F1(x1) 
     , F2(x2) {} 

    using F1::operator(); 
}; 

template <typename F1, typename F2> 
auto get(F1 x1, F2 x2) -> Overload<F1, F2> 
{ 
    return Overload<F1, F2>(x1, x2); 
} 


int main(int argc, char *argv[]) 
{ 
    auto f = get(Functor1(), Functor2()); 

    f(); 
#ifdef FUNCTOR 
    f(2); // this one doesn't work IMHO correctly 
#endif 

    auto f1 = get(
        []() { std::cout << "lambda1::operator()()\n"; }, 
        [](int) { std::cout << "lambda2::operator()(int)\n"; } 
       ); 
    f1(); 
    f1(2); // this one works but I don't know why 


    return 0; 
} 

standardowe stanowi:

Typ lambda ekspresji (który jest również rodzaj obiektu zamknięcia) jest unikalnym, Nienazwana non klasę Union Typ

Tak więc każdy typ Lambdy powinien być unikalny.

Nie mogę wyjaśnić, dlaczego tak jest: czy ktoś może rzucić trochę światła na to?

+9

Witamy przepełnienie stosu, bardzo ładne pytanie! – Mat

+1

Bardzo. Jesteśmy ciężkim tłumem, a wielu z nas nauczyło nas nowych rzeczy. – zneak

Odpowiedz

33

Oprócz klasy operator(), klasa zdefiniowana przez puszkę lambda (w odpowiednich okolicznościach) zapewnia konwersję wskaźnika na funkcję. Okoliczność (a przynajmniej pierwsza) jest taka, że ​​lambda nie może niczego uchwycić.

Jeśli dodać do przechwytywania:

auto f1 = get(
       []() { std::cout << "lambda1::operator()()\n"; }, 
       [i](int) { std::cout << "lambda2::operator()(int)\n"; } 
      ); 
f1(); 
f1(2); 

... konwersja do pointer to function już nie jest, więc próbuje skompilować powyższy kod daje błąd prawdopodobnie oczekiwany przez cały czas:

trash9.cpp: In function 'int main(int, char**)': 
trash9.cpp:49:9: error: no match for call to '(Overload<main(int, char**)::<lambda()>, main(int, char**)::<lambda(int)> >) (int)' 
trash9.cpp:14:8: note: candidate is: 
trash9.cpp:45:23: note: main(int, char**)::<lambda()> 
trash9.cpp:45:23: note: candidate expects 0 arguments, 1 provided 
+0

Czekaj. Czy możesz powiedzieć, że możesz dziedziczyć po wskaźnikach funkcji? – zneak

+0

@zneak: Nie, wcale. Możesz dziedziczyć z lambda, ponieważ definiuje klasę, a nie funkcję. –

+0

'Przeciążenie struktury: publiczne F1, publiczne F2', z F1 i F2 wydedukowane jako typy wskaźników funkcji, zgodnie z twoją hipotezą, jeśli przeczytałem to dobrze. – zneak

14

Lambda generuje klasę funktora.

Rzeczywiście, możesz czerpać z lambd i mieć polimorficzne lambdy!

#include <string> 
#include <iostream> 

int main() 
{ 
    auto overload = make_overload(
     [](int i)   { return '[' + std::to_string(i) + ']'; }, 
     [](std::string s) { return '[' + s + ']'; }, 
     []     { return "[void]"; } 
     ); 

    std::cout << overload(42)    << "\n"; 
    std::cout << overload("yay for c++11") << "\n"; 
    std::cout << overload()    << "\n"; 
} 

Drukuje

[42] 
[yay for c++11] 
[void] 

jak?

template <typename... Fs> 
    Overload<Fs...> make_overload(Fs&&... fs) 
{ 
    return { std::forward<Fs>(fs)... }; 
} 

Oczywiście ... to wciąż kryje magię. To jest klasa Overload że „magicznie” wywodzi się z wszystkimi lambdas i naraża odpowiedni operator():

#include <functional> 

template <typename... Fs> struct Overload; 

template <typename F> struct Overload<F> { 
    Overload(F&& f) : _f(std::forward<F>(f)) { } 

    template <typename... Args> 
    auto operator()(Args&&... args) const 
    -> decltype(std::declval<F>()(std::forward<Args>(args)...)) { 
     return _f(std::forward<Args>(args)...); 
    } 

    private: 
    F _f; 
}; 

template <typename F, typename... Fs> 
    struct Overload<F, Fs...> : Overload<F>, Overload<Fs...> 
{ 
    using Overload<F>::operator(); 
    using Overload<Fs...>::operator(); 

    Overload(F&& f, Fs&&... fs) : 
     Overload<F>(std::forward<F>(f)), 
     Overload<Fs...>(std::forward<Fs>(fs)...) 
    { 
    } 
}; 

template <typename... Fs> 
    Overload<Fs...> make_overload(Fs&&... fs) 
{ 
    return { std::forward<Fs>(fs)... }; 
} 

zobaczyć to Live on Coliru

+8

Chociaż to jest interesujące, nie widzę jak odpowiada na pytanie: – Nemo

+0

@Nemo ah Najwyraźniej źle odczytałem to pytanie, ale kod w mojej odpowiedzi wydaje się przeczyć twierdzeniu OP, że "jeśli pochodzę z dwóch funkcji Lambda, to nie jest już prawdą: mogę uzyskać dostęp operator() również z F2. "Jeśli zgubisz' using base :: operator() 'lin e kod już nie działa. Przypuszczam, że dzieje się tak dlatego, że zdecydowałem się na odziedziczone dziedzictwo, a nie na wielokrotne dziedziczenie – sehe

Powiązane problemy