Dlaczego nie mogę utworzyć wektora lambd (tego samego typu) w C ++ 11?

88

Próbowałem stworzyć wektor lambda, ale nie udało mi się:

auto ignore = [&]() { return 10; };  //1
std::vector<decltype(ignore)> v;     //2
v.push_back([&]() { return 100; });  //3

Do linii nr 2 kompiluje się dobrze . Ale wiersz # 3 podaje błąd kompilacji :

błąd: brak funkcji dopasowującej dla wywołania 'std :: vector <main () :: <lambda () >> :: push_back (main () :: <lambda ()>)'

Nie chcę wektora wskaźników funkcji ani wektora obiektów funkcji. Jednak wektor obiektów funkcyjnych, które hermetyzują prawdziwe wyrażenia lambda, byłby dla mnie odpowiedni. czy to możliwe?

Nawaz
źródło
23
„Nie chcę wektora wskaźników funkcji ani wektora obiektów funkcji”. Ale o to prosiłeś. Lambda to obiekt funkcji.
Nicol Bolas,

Odpowiedzi:

135

Każda lambda ma inny typ - nawet jeśli ma ten sam podpis. Musisz użyć kontenera hermetyzującego w czasie wykonywania, na przykład std::functionjeśli chcesz zrobić coś takiego.

na przykład:

std::vector<std::function<int()>> functors;
functors.push_back([&] { return 100; });
functors.push_back([&] { return  10; });
Szczeniak
źródło
52
Zarządzanie stuosobowym zespołem programistów brzmi dla mnie bardziej jak koszmar :)
Jeremy Friesner
10
Nie zapominaj również, że lambdy bez przechwytywania (styl []) mogą degradować się do wskaźników funkcji. Mógłby więc przechowywać tablicę wskaźników funkcji tego samego typu. Zauważ, że VC10 jeszcze tego nie implementuje.
Nicol Bolas,
Nawiasem mówiąc, czy w tych przykładach nie powinno się używać opcji mniej przechwytywania? Czy jest to konieczne? - Nawiasem mówiąc, Lambda bez przechwytywania do wskaźnika funkcji wydaje się być obsługiwana w VC11. Nie przetestowałem tego jednak.
Klaim
2
Czy można stworzyć wektor przechowujący funkcje innego typu? tzn. zamiast ograniczać się do tego std::function<int(), czy mógłbym używać różnych prototypów funkcji?
manatttta
2
@manatttta Jaki byłby sens? Istnieją pojemniki do przechowywania obiektów tego samego typu, do ich wspólnego organizowania i manipulowania nimi. Równie dobrze możesz zapytać „czy mogę utworzyć vectormagazyn zarówno std::functioni std::string?” Odpowiedź jest taka sama: nie, ponieważ nie jest to zamierzone zastosowanie. Możesz użyć klasy w stylu `` wariant '', aby wykonać wystarczające wymazanie typu, aby umieścić różne rzeczy w kontenerze, jednocześnie włączając metodę dla użytkownika, aby określić `` prawdziwy '' typ i tym samym wybrać, co zrobić z (np. Jak wywołać) każdy element ... ale znowu, po co iść do takich długości? Czy jest jakieś prawdziwe uzasadnienie?
underscore_d
40

Wszystkie wyrażenia lambda mają inny typ, nawet jeśli są identyczne znak po znaku . Wsuwasz do wektora lambdę innego typu (ponieważ jest to inne wyrażenie), a to oczywiście nie zadziała.

Jednym z rozwiązań jest utworzenie wektora std::function<int()>zamiast.

auto ignore = [&]() { return 10; };
std::vector<std::function<int()>> v;
v.push_back(ignore);
v.push_back([&]() { return 100; });

Z drugiej strony, nie jest dobrym pomysłem używanie go, [&]gdy niczego nie przechwytujesz.

R. Martinho Fernandes
źródło
14
Nie potrzeba ()lambd, które nie przyjmują żadnych argumentów.
Puppy
18

Chociaż to, co powiedzieli inni, jest istotne, nadal można zadeklarować i użyć wektora lambda, chociaż nie jest to zbyt przydatne:

auto lambda = [] { return 10; };
std::vector<decltype(lambda)> vec;
vec.push_back(lambda);

Więc możesz tam przechowywać dowolną liczbę lambd, o ile jest to kopia / przeniesienie lambda!

Luc Danton
źródło
Co może być przydatne, jeśli odpowiedź zwrotna ma miejsce w pętli z różnymi parametrami. Prawdopodobnie do leniwych celów oceny.
MaHuJa
7
Nie, nie umieszczasz parametrów w wektorze, tylko obiekt funkcji .. Więc byłby to wektor ze wszystkimi kopiami tej samej lambdy
hariseldon78
16

Jeśli twoja lambda jest bezstanowa, tj. [](...){...}C ++ 11 pozwala jej zdegradować ją do wskaźnika funkcji. Teoretycznie kompilator zgodny z C ++ 11 byłby w stanie skompilować to:

auto ignore = []() { return 10; };  //1 note misssing & in []!
std::vector<int (*)()> v;     //2
v.push_back([]() { return 100; });  //3
MSN
źródło
4
Dla porządku auto ignore = *[] { return 10; };zrobiłoby ignoreto int(*)().
Luc Danton,
1
@Luc, och, to jest obrzydliwe! Kiedy to dodali?
MSN
3
Cóż, ponieważ funkcja konwersji, która pozwala w pierwszej kolejności wziąć wskaźnik funkcji, jest zobowiązana nie być explicit, wyłuskiwanie wyrażenia lambda jest poprawne i usuwa odniesienie do wskaźnika wynikającego z konwersji. Następnie używając autorozpadów, które odwołują się z powrotem do wskaźnika. (Używając auto&lub auto&&zachowałby odniesienie.)
Luc Danton
Ach ... Dereferencja wynikowego wskaźnika. To ma sens. Czy zaginięcie było ()zamierzone czy przypadkowe?
MSN
Celowo wyrażenie lambda jest równoważne (ale o dwa znaki krótsze).
Luc Danton,
6

Można by skorzystać z funkcji generującej lambdę (zaktualizowanej poprawką sugerowaną przez Nawaza):

#include <vector>
#include <iostream>

int main() {
    auto lambda_gen = [] (int i) {return [i](int x){ return i*x;};} ;

    using my_lambda = decltype(lambda_gen(1));

    std::vector<my_lambda> vec;

    for(int i = 0; i < 10; i++) vec.push_back(lambda_gen(i));

    int i = 0;

    for (auto& lambda : vec){
        std::cout << lambda(i) << std::endl;
        i++;
    }
}

Ale myślę, że w zasadzie stworzyłeś swoją własną klasę w tym momencie. W przeciwnym razie, jeśli lambdy mają zupełnie inne caputres / args itp., Prawdopodobnie będziesz musiał użyć krotki.

przedpotopowy
źródło
Fajny pomysł, aby opakować go w funkcję, taką jak lambda_genktóra z kolei może być samą lambdą. Jednak auto a = lambda_gen(1);wykonuje niepotrzebne wezwanie, którego można uniknąć, jeśli to napiszemy decltype(lambda_gen(1)).
Nawaz
Czy to jednak nadal nie powoduje dodatkowego połączenia? Kolejną drobną kwestią jest to, że pytanie brzmi C ++ 11, więc należałoby dodać końcowy typ powrotu do funkcji, o której myślę.
przedpotopowy
Nie. Wszystko w środku decltype jest nieocenione , więc połączenie nie jest faktycznie wykonywane. To samo dotyczy sizeofrównież. Ponadto ten kod nie będzie działał w C ++ 11, nawet jeśli dodasz końcowy typ powrotu !!
Nawaz
4

Każda lambda jest innego typu. Musisz użyć std::tuplezamiast std::vector.

Paul Fultz II
źródło