Jak działa generyczna lambda w C ++ 14?

114

Jak działa generyczna lambda ( autosłowo kluczowe jako typ argumentu) w standardzie C ++ 14?

Czy jest oparty na szablonach C ++, w których dla każdego innego typu argumentu kompilator generuje nową funkcję o tej samej treści, ale zastępowanych typach (polimorfizm w czasie kompilacji), czy jest bardziej podobny do generycznych Javy (wymazywanie typów)?

Przykład kodu:

auto glambda = [](auto a) { return a; };
sasha.sochka
źródło
6
Naprawiony do C ++ 14, pierwotnie używany C ++ 11, o którym mowa
sasha.sochka

Odpowiedzi:

130

Ogólne lambdy zostały wprowadzone w C++14.

Po prostu typ zamknięcia zdefiniowany przez wyrażenie lambda będzie miał operator wywołania oparty na szablonie zamiast zwykłego, nieszablonowego operatora wywołania C++11lambd (oczywiście, gdy autopojawia się przynajmniej raz na liście parametrów).

Więc twój przykład:

auto glambda = [] (auto a) { return a; };

Stworzy glambdainstancję tego typu:

class /* unnamed */
{
public:
    template<typename T>
    T operator () (T a) const { return a; }
};

Punkt 5.1.2 / 5 wersji standardowej wersji n3690 języka C ++ 14 określa, w jaki sposób definiowany jest operator wywołania typu zamknięcia danego wyrażenia lambda:

Typ zamknięcia dla nieogólnego wyrażenia lambda ma publiczny operator wywołania funkcji wbudowanej (13.5.4), którego parametry i typ zwracany są opisane odpowiednio przez klauzulę deklaracji parametru i typ zwracany na końcu. W przypadku generycznej lambdy typ zamknięcia ma publiczny szablon elementu członkowskiego wywołania funkcji wbudowanej (14.5.2), którego lista parametrów szablonu składa się z jednego wymyślonego typu szablon-parametr dla każdego wystąpienia auto w klauzuli deklaracji parametru, w kolejności pojawiania się. Wynaleziony typ parametru-szablonu jest pakietem parametrów, jeśli odpowiednia deklaracja parametru deklaruje pakiet parametrów funkcji (8.3.5). Zwracany typ i parametry funkcji szablonu operatora wywołania funkcji są wyprowadzane z końcowego typu zwracanego-wyrażenia lambda i klauzuli-deklaracji parametru, zastępując każde wystąpienie auto w specyfikatorach decl klauzuli deklaracji parametru nazwą odpowiadający wynaleziony szablon-parametr.

Wreszcie:

Czy jest podobny do szablonów, w których dla każdego innego typu argumentu kompilator generuje funkcje o tej samej treści, ale zmienionych typach, czy też jest bardziej podobny do generycznych Javy?

Jak wyjaśnia powyższy akapit, generyczne lambdy są po prostu cukrem syntaktycznym dla unikalnych, nienazwanych funktorów z operatorem wywołania opartym na szablonie. To powinno odpowiedzieć na Twoje pytanie :)

Andy Prowl
źródło
7
Jednak pozwalają one również na lokalnie zdefiniowaną klasę z metodą szablonu. Co jest nowe.
Yakk - Adam Nevraumont
2
@Yakk: Czy ograniczenie dotyczące szablonów lokalnych funkcji nie zostało już całkowicie usunięte wraz z C ++ 11?
Sebastian Mach,
2
@phresnel: Nie, to ograniczenie nie zostało zniesione
Andy Prowl
1
@AndyProwl: Zdaję sobie sprawę z mojego błędu. To, co faktycznie zostało zniesione, to użycie lokalnych typów jako argumentów szablonów (jak w int main () { struct X {}; std::vector<X> x; })
Sebastian Mach,
1
@phresnel: Tak, to rzeczywiście się zmieniło
Andy Prowl,
25

Niestety nie są one częścią C ++ 11 ( http://ideone.com/NsqYuq ):

auto glambda = [](auto a) { return a; };

int main() {}

Przy g ++ 4,7:

prog.cpp:1:24: error: parameter declared auto
...

Jednak sposób, w jaki można to zaimplementować w C ++ 14 zgodnie z propozycją Portland dla generycznych lambd :

[](const& x, & y){ return x + y; }

Dałoby to w dużej mierze zwykłe utworzenie anonimowej klasy funktora, ale przy braku typów kompilator wyemitowałby element członkowski oparty na szablonie operator():

struct anonymous
{
    template <typename T, typename U>
    auto operator()(T const& x, U& y) const -> decltype(x+y)
    { return x + y; }
};

Lub zgodnie z nowszą propozycją propozycji ogólnych (polimorficznych) wyrażeń lambda

auto L = [](const auto& x, auto& y){ return x + y; };

--->

struct /* anonymous */
{
    template <typename T, typename U>
    auto operator()(const T& x, U& y) const // N3386 Return type deduction
    { return x + y; }
} L;

Więc tak, dla każdej permutacji parametrów powstałaby nowa instancja, jednak członkowie tego funktora nadal byliby współużytkowani (tj. Przechwycone argumenty).

Sebastian Mach
źródło
6
Ta propozycja, aby zezwolić na usunięcie specyfikatora typu, jest całkowicie groteskowa.
Wyścigi lekkości na orbicie,
Weszli z g ++ - 4,9 . Musisz zaopatrzyć -std=c++1y.
emsr
Nie zdawałem sobie sprawy, że ideone nie ma jeszcze gcc-4.9 i C ++ 14.
emsr
pytanie: czy to automa takie same zasady odliczania jak klasyczne auto? Jeśli odniesiemy się do analogii opartej na szablonach, oznaczałoby to, że auto nie jest automatyczne, to te same zasady, co dedukcja typu szablonu. Wtedy pytanie brzmi: czy odliczenie szablonowe jest równoważne auto?
v.oddou,
@ v.oddou: „Klasyczne auto” jest dobre. Dla mnie „klasyczne auto” oznacza „zmienną stosu” i było kiedyś używane w przeciwieństwie do staticlub register:) Tak czy inaczej, użycie autotam oznacza, że ​​pod maską generowany jest normalny szablon. W rzeczywistości lambda zostanie wewnętrznie zastąpiona przez kompilator klasą funktora, a autoparametr oznacza, że template <T> ... (T ...)zostanie wyemitowany.
Sebastian Mach,
17

Jest to proponowana funkcja C ++ 14 (nie w C ++ 11) podobna (lub nawet równoważna) do szablonów. Na przykład N3559 dostarcza tego przykładu:

Na przykład to ogólne wyrażenie lambda zawierające instrukcję:

auto L = [](const auto& x, auto& y){ return x + y; };

może skutkować utworzeniem typu zamknięcia i obiektu, który zachowuje się podobnie do struktury poniżej:

struct /* anonymous */
{
    template <typename T, typename U>
    auto operator()(const T& x, U& y) const // N3386 Return type deduction
    { return x + y; }
} L;
Cassio Neri
źródło