Czy jest jakiś powód, aby nie używać globalnych lambdas?

89

Mieliśmy funkcję, która wykorzystywała wewnętrzną lambda, która nie przechwytuje, np .:

void foo() {
  auto bar = [](int a, int b){ return a + b; }

  // code using bar(x,y) a bunch of times
}

Teraz funkcjonalność zaimplementowana przez lambda stała się potrzebna gdzie indziej, więc zamierzam foo()przenieść lambda z zakresu globalnego / przestrzeni nazw. Mogę zostawić go jako lambda, ustawiając opcję kopiuj-wklej lub zmienić na odpowiednią funkcję:

auto bar = [](int a, int b){ return a + b; } // option 1
int bar(int a, int b){ return a + b; } // option 2

void foo() {
  // code using bar(x,y) a bunch of times
}

Zmiana tej funkcji na właściwą jest banalna, ale zastanawiałem się, czy istnieje jakiś powód, aby nie zostawiać jej jako lambda? Czy jest jakiś powód, aby nie używać wszędzie lambd zamiast „zwykłych” funkcji globalnych?

Baruch
źródło
Zakładam, że przechwycenie niezamierzonych zmiennych doprowadziłoby do wielu błędów, gdyby lambdy nie były ostrożnie używane
Macroland
kilka argumentów za tym youtube.com/watch?v=Ft3zFk7VPrE
sudo rm -rf slash

Odpowiedzi:

61

Jest jeden bardzo ważny powód, aby nie używać globalnych lambdas: ponieważ nie jest to normalne.

Regularna składnia funkcji C ++ istnieje już od czasów C. Programiści od dziesięcioleci wiedzą, co oznacza ta składnia i jak działają (choć wprawdzie cała ta funkcja rozkładania funkcji na wskaźnik czasami gryzie nawet doświadczonych programistów). Jeśli programista C ++ o dowolnym poziomie umiejętności wykraczającym poza „kompletny nowicjusz” widzi definicję funkcji, wie, co dostaje.

Globalna lambda to zupełnie inna bestia. Ma inne zachowanie niż zwykła funkcja. Lambdy są obiektami, podczas gdy funkcje nie. Mają typ, ale ten typ różni się od rodzaju ich funkcji. I tak dalej.

Więc teraz podniosłeś poprzeczkę w komunikacji z innymi programistami. Programista C ++ musi zrozumieć lambdas, jeśli chce zrozumieć, co robi ta funkcja. I tak, to jest rok 2019, więc porządny programista C ++ powinien mieć pojęcie, jak wygląda lambda. Ale wciąż jest to wyższy pasek.

I nawet jeśli to zrozumieją, pytanie w umyśle tego programisty brzmi ... dlaczego pisarz tego kodu napisał to w ten sposób? A jeśli nie masz dobrej odpowiedzi na to pytanie (na przykład, ponieważ wyraźnie chcesz zabronić przeciążania, jak w punktach dostosowywania Ranges), powinieneś użyć wspólnego mechanizmu.

W razie potrzeby preferuj oczekiwane rozwiązania od nowatorskich. Skorzystaj z najmniej skomplikowanej metody zdobycia punktu.

Nicol Bolas
źródło
9
„od czasów C” Dawno temu mój przyjaciel
Lekkość ściga się na orbicie
4
@LightnessRaces: Moim głównym celem było wyrażenie, że składnia funkcji C ++ istnieje już od C, więc wiele osób wie, co to jest przez kontrolę.
Nicol Bolas,
2
Zgadzam się z całą tą odpowiedzią, z wyjątkiem pierwszego zdania. „To nie jest normalne” to niejasne i mgliste stwierdzenie. Być może miałeś na myśli to w znaczeniu „jest to rzadkie i mylące”?
einpoklum
1
@einpoklum: C ++ ma wiele rzeczy, które można by uznać za „nietypowe i mylące”, które wciąż są całkiem „normalne” w ich domenie (patrz głębokie metaprogramowanie szablonów). Myślę, że w tym przypadku „normalne” jest całkowicie poprawne; nie jest to coś, co mieści się w „normach” programowania C ++, zarówno historycznie, jak i dziś.
Nicol Bolas,
@NicolBolas: Ale w tym sensie jest to okrągłe rozumowanie: OP zastanawia się, czy powinniśmy zastosować rzadką praktykę. Rzadkie praktyki nie są „normą”, prawie z definicji. Ale nm.
einpoklum
53

Mogę wymyślić kilka powodów, dla których chcesz uniknąć globalnych lambd jako zastępczych zastępców dla zwykłych funkcji:

  • zwykłe funkcje mogą być przeciążone; lambdas nie może (istnieją jednak techniki symulujące to)
  • Pomimo faktu, że są one podobne do funkcji, nawet taka nie przechwytująca lambda zajmie pamięć (zwykle 1 bajt dla nie przechwytywania).
    • jak wskazano w komentarzach, nowoczesne kompilatory zoptymalizują tę pamięć zgodnie z zasadą „ tak jak gdyby”

„Dlaczego nie powinienem używać lambda zamiast stanowych funktorów (klas)?”

  • klasy mają po prostu mniej ograniczeń niż lambdas i dlatego powinny być pierwszą rzeczą, do której sięgasz
    • (dane publiczne / prywatne, przeciążenie, metody pomocnicze itp.)
  • jeśli lambda ma stan, tym trudniej jest zrozumieć, kiedy staje się globalna.
    • Powinniśmy preferować utworzenie instancji klasy w możliwie najwęższym zakresie
  • już trudno jest przekonwertować nie przechwytującą lambdę na wskaźnik funkcji i nie jest możliwe dla lambda, która określa cokolwiek w jej przechwytywaniu.
    • klasy dają nam prosty sposób tworzenia wskaźników funkcji, a także są tym, z czym wielu programistów czuje się lepiej
  • Lambdas z dowolnym przechwytywaniem nie może być konstruowany domyślnie (w C ++ 20. Wcześniej w żadnym wypadku nie było domyślnego konstruktora)
AndyG
źródło
Istnieje również domyślny „ten” wskaźnik do lambda (nawet nie przechwytującego), co daje dodatkowy parametr podczas wywoływania lambda w porównaniu do wywołania funkcji.
1201ProgramAlarm
@ 1201ProgramAlarm: To dobra uwaga; uzyskanie wskaźnika funkcji dla lambda może być znacznie trudniejsze (chociaż niepochodzące lambdy mogą rozpaść się na zwykłe wskaźniki funkcji)
AndyG
3
Z drugiej strony, jeśli jest przekazywany gdzieś zamiast bezpośrednio wywoływany, posiadanie lambda zamiast funkcji sprzyja wstawianiu. Oczywiście można spakować wskaźnik funkcji std::integral_constantdo tego ...
Deduplicator
@Deduplicator: „ posiadanie lambda zamiast funkcji promuje wstawianie”. Żeby było jasne, dotyczy to tylko sytuacji, gdy „gdzieś” jest szablonem, który przyjmuje funkcję, którą wywołuje, jako dowolny typ wywoływalny, a nie wskaźnik funkcji.
Nicol Bolas,
2
@AndyG Zapomniałeś zasady „jak gdyby”: programy, które nie żądają wielkości lambda (ponieważ… dlaczego ?!), ani nie tworzą do niej wskaźnika, nie muszą (i generalnie nie robią) przydziel na to miejsce.
Konrad Rudolph,
9

Po zapytaniu pomyślałem o przyczynie, aby tego nie robić: ponieważ są to zmienne, są one podatne na statyczne zlecenie inicjalizacji Fiasco ( https://isocpp.org/wiki/faq/ctors#static-init-order ), które mogłyby powodować błędy w linii.

Baruch
źródło
Państwo mogłoby uczynić je constexprlambdas ... prawdziwy punkt brzmi: Wystarczy użyć funkcji.
einpoklum,
8

Czy jest jakiś powód, aby nie używać wszędzie lambd zamiast „zwykłych” funkcji globalnych?

Problem o pewnym poziomie złożoności wymaga rozwiązania o co najmniej tej samej złożoności. Ale jeśli istnieje mniej skomplikowane rozwiązanie tego samego problemu, wówczas naprawdę nie ma uzasadnienia dla korzystania z bardziej złożonego. Po co wprowadzać złożoność, której nie potrzebujesz?

Między lambda a funkcją funkcja jest po prostu mniej złożonym rodzajem bytu tych dwóch. Nie musisz uzasadniać, że nie używasz lambda. Musisz uzasadnić użycie jednego. Wyrażenie lambda wprowadza typ zamknięcia, który jest nienazwanym typem klasy ze wszystkimi zwykłymi specjalnymi funkcjami składowymi, operatorem wywołania funkcji oraz, w tym przypadku, niejawnym operatorem konwersji na wskaźnik funkcji i tworzy obiekt tego typu. Inicjowanie kopii zmiennej globalnej z wyrażenia lambda po prostu robi znacznie więcej niż tylko definiowanie funkcji. Definiuje typ klasy z sześcioma niejawnie zadeklarowanymi funkcjami, definiuje dwie dodatkowe funkcje operatora i tworzy obiekt. Kompilator musi zrobić znacznie więcej. Jeśli nie potrzebujesz żadnej z funkcji lambda, nie używaj lambda…

Michael Kenzel
źródło
6

Lambda to funkcje anonimowe .

Jeśli używasz nazwanej lambda, oznacza to, że zasadniczo używasz nazwanej funkcji anonimowej. Aby uniknąć tego oksymoronu, równie dobrze możesz użyć funkcji.

Eric Duminil
źródło
1
To wydaje się być argumentem z terminologii, tj. mylące nazewnictwo i znaczenie. Można to łatwo obalić, definiując, że nazwana lambda nie jest już anonimowa (duh). Nie chodzi tu o to, w jaki sposób używana jest lambda, chodzi o to, że „funkcja anonimowa” jest myląca i nie jest już dokładna, gdy lambda jest związana z nazwą.
Konrad Rudolph,
@KonradRudolph: To nie jest tylko terminologia, dlatego właśnie zostały stworzone. Przynajmniej z historycznego punktu widzenia lambdas są funkcjami anonimowymi i dlatego mylące jest ich nazywanie, zwłaszcza gdy zamiast tego oczekiwane są funkcje.
Eric Duminil,
1
Nie Lambdas są rutynowo nazywane (zwykle lokalnie), nie ma w tym nic mylącego.
Konrad Rudolph,
1
Nie zgadzam się z anonimowymi funkcjami , jest to bardziej funktor, ale w każdym razie nie widzę problemu z (nazwaną) instancją zamiast zmuszania do używania ich tylko jako referencji do wartości. (jak należy uważać również w zakresie lokalnym).
Jarod42,
5

czy istnieje jakiś powód, aby nie zostawiać go jako lambda? Czy jest jakiś powód, aby nie używać wszędzie lambd zamiast „zwykłych” funkcji globalnych?

Kiedyś używaliśmy funkcji zamiast globalnego funktora, więc łamie to spójność i zasadę najmniejszego zdziwienia .

Główne różnice to:

  • funkcje mogą być przeciążone, podczas gdy funktory nie.
  • funkcje można znaleźć w ADL, a nie funktory.
Jarod42
źródło