Подтвердить что ты не робот

Форвардное объявление лямбда в С++

В С++ можно отделить объявление и определение функций. Например, вполне нормально объявлять функцию:

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 на С#, но я не очень использовал их на С++.

4b9b3361

Ответ 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);
}

с одной тонкой разницей, что даже ссылки на захват лямбда могут быть скопированы (обычно классы, содержащие ссылки как члены, не могут).