Форвардное объявление лямбда в С++
В С++ можно отделить объявление и определение функций. Например, вполне нормально объявлять функцию:
int Foo(int x);
в Foo.h
и реализовать его в Foo.cpp
. Можно ли что-то сделать с лямбдами? Например, определите
std::function<int(int)> bar;
в bar.h
и реализовать его в bar.cpp
как:
std::function<int(int)> bar = [](int n)
{
if (n >= 5)
return n;
return n*(n + 1);
};
Отказ от ответственности. У меня есть опыт работы с lambdas на С#, но я не очень использовал их на С++.
Ответы
Ответ 1
Вы не можете отделить объявление и определение lambdas, ни один из них не объявите его. Его тип - это безымянный тип закрытия, который объявляется с помощью выражения лямбда. Но вы можете сделать это с помощью std:: function, который предназначен для хранения любой вызываемой цели, включая лямбда-выражения.
Как показано в примере кода, который вы использовали std::function
, просто обратите внимание, что для этого случая bar
действительно существует глобальная переменная, и вам нужно использовать extern
в заголовочном файле, чтобы сделать его декларацией (а не определение).
// bar.h
extern std::function<int(int)> bar; // declaration
и
// bar.cpp
std::function<int(int)> bar = [](int n) // definition
{
if (n >= 5) return n;
return n*(n + 1);
};
Заметим еще раз, что это не отдельное объявление и определение лямбда; Он просто разделяет объявление и определение глобальной переменной bar
с типом std::function<int(int)>
, который инициализируется из выражения лямбда.
Ответ 2
Строго говоря, вы не можете
Цитата из ссылка cpp
Лямбда-выражение представляет собой выражение prvalue, значение которого (до С++ 17), результатом которого является объект (с С++ 17) неназванный временный объект уникального неназванного не-объединенного типа неагрегатного класса, известного как закрытие тип, который объявляется (для целей ADL) в наименьшем область видимости блока, область видимости класса или область пространства имен, которая содержит лямбда Выражение
Таким образом, лямбда является неназванным временным объектом. Вы можете привязать лямбда к объекту l-value (например, std::function
) и регулярными правилами об объявлении переменных вы можете отделить объявление и определение.
Ответ 3
Объявление Forward не является правильным термином, потому что lambdas в С++ - это объекты, а не функции. Код:
std::function<int(int)> bar;
объявляет переменную, и вы не должны ее назначать (этот тип имеет значение по умолчанию "указатель на отсутствие функции" ). Вы можете скомпилировать даже его вызовы... например, код:
#include <functional>
#include <iostream>
int main(int argc, const char *argv[]) {
std::function<int(int)> bar;
std::cout << bar(21) << "\n";
return 0;
}
будет компилироваться чисто (но, конечно, будет вести себя безумно во время выполнения).
Это означает, что вы можете назначить лямбда совместимой переменной std::function
и добавить, например:
bar = [](int x){ return x*2; };
прямо перед вызовом будет выполнена программа, которая компилируется и генерируется как выход 42.
Несколько неочевидных вещей, которые могут быть удивительными в отношении lambdas в С++ (если вы знаете другие языки, которые имеют это понятие), заключаются в том, что
-
Каждый лямбда [..](...){...}
имеет другой несовместимый тип, даже если подпись абсолютно идентична. Например, вы не можете объявить параметр лямбда-типа, потому что единственным способом было бы использовать что-то вроде decltype([] ...)
, но тогда не было бы способа вызвать функцию, так как любая другая форма []...
на сайте вызова была бы несовместимой. Это разрешено std::function
, поэтому, если вам нужно передать лямбда или вокруг них в контейнерах, вы должны использовать std::function
.
-
Lambdas может захватывать локальные объекты по значению (но они const
, если вы не объявляете lambda mutable
) или по ссылке (но гарантировать, что время жизни ссылочного объекта не будет меньше срока службы лямбда до программиста). У С++ нет сборщика мусора, и это то, что необходимо для правильной решения проблемы "вверх" (вы можете работать, захватывая интеллектуальные указатели, но вы должны обратить внимание на циклы ссылок, чтобы избежать утечек).
-
В отличие от других языков lambdas можно копировать, и при их копировании вы делаете снимок своих внутренних захваченных переменных. Это может быть очень удивительно для изменяемого состояния, и я думаю, что причина, по которой зафиксированные значения значения по умолчанию const
по умолчанию.
Способ рационализации и запоминания многих деталей о lambdas заключается в том, что код вроде:
std::function<int(int)> timesK(int k) {
return [k](int x){ return x*k; };
}
в основном похож на
std::function<int(int)> timesK(int k) {
struct __Lambda6502 {
int k;
__Lambda6502(int k) : k(k) {}
int operator()(int x) {
return x * k;
}
};
return __Lambda6502(k);
}
с одной тонкой разницей, что даже ссылки на захват лямбда могут быть скопированы (обычно классы, содержащие ссылки как члены, не могут).