Каково время жизни цели указателя на функцию, указывающее на лямбда?

Извините, это длинный вопрос, но позвольте мне сломать его:

Стандарт С++ гарантирует, что:

void (*Ptr)(void) = [] {};
return Ptr;

все еще будет определено поведение?

Я понимаю, что для закрытия он будет определен, потому что этот объект закрытия перемещается/копируется по значению; но, хотя я знаю, что "регулярная" функция имеет бесконечное/нет времени жизни, имеет ли цель Ptr то же самое? Или он разрушен и воссоздан с каждым экземпляром лямбда?

По этой причине я не могу использовать lambdas как обратные вызовы, если нет. Я хочу знать.

Ответ 1

Объекты имеют время жизни; функций нет. Функции не живут и не умирают; они всегда существуют. Таким образом, функция не может выходить "из области видимости", и функция, на которую указывает предыдущий действительный указатель функции, исчезает. Независимо от того, откуда они берутся, указатели на функции всегда действительны.

Теперь это игнорирует динамическую загрузку и т.д., но это нестандартное поведение.

Указатель функции, который вы возвращаете из лямбда, является указателем на функцию. Это не особенное или волшебное. Поэтому он ведет себя не иначе, как любой другой указатель функции.


Возможно ли, что результат преобразования в void (*)() указывает на то, что вызывает функцию-член, связанную с каким-либо объектом?

Это гораздо более сложный вопрос. Тот, который стандарт кажется довольно недооцененным. Стандарт только говорит:

адрес функции, которая при вызове имеет тот же эффект, что и вызов вызывающего оператора функции замыкания.

То, что означает "тот же эффект" , - это вопрос. Можно утверждать, что "тот же эффект" означает выполнение того, что сделал оператор вызова функции, выполняя ту же последовательность утверждений. Можно также утверждать, что "тот же эффект" означает вызов самого объекта замыкания.

Последний случай может показаться сложным для реализации, но помните, что магия компилятора может быть использована. Закрытие может возвращать указатель функции, специфичный для экземпляра, который назначается закрытием по запросу. Или некоторые-такие.

Ненормативный текст, по-видимому, не очень подходит по этому вопросу. Существует пример закрытия для (generic) лямбда, который говорит следующее:

template<class T> auto operator()(T t) const { ... }
template<class T> static auto lambda_call_operator_invoker(T a) {
// forwards execution to operator()(a) and therefore has
// the same return type deduced
...
}

В рассматриваемом комментарии предлагается пересылка, но для этого требуется, чтобы статический вызов создавал новый экземпляр лямбда для выполнения пересылки с помощью.

В целом, стандарт не дает понять, является ли обращение к сгенерированному указателю функции после уничтожения закрытия.

Ответ 2

лямбда-функция - это просто синтаксический сахар для реальной функции или функтора (т.е. объект с operator() и некоторые члены, определенные обычно во время построения). Таким образом, такая функция или метод статически определяется во время компиляции.

Хотя стандарт может не указывать полностью точный способ реализации, как отметил @NicolBolas, кажется, что практические реализации следуют строжайшему руководству: лямбда без контекста может быть преобразована в простой указатель на функцию, не создается промежуточный объект, ни в месте лямбда-дефиниции, ни в месте вызова. Я только что проверил его (еще раз) для gcc и clang, и я почти уверен, что MSVC делает то же самое.

Примечание. Остальное относится к лямбдам с контекстами, и хотя для меня это выглядит более интересным и практичным, вопрос явно касается неконкурентных лямбдов.

Контекст сохраняется в лямбда (подумайте о объекте-функторе с некоторыми значимыми аргументами, полученными при построении объекта). Таким образом, если вы передадите некоторые ссылки или указатели на контекст, эти ссылки и указатели (например, this) не будут автоматически продлевать время жизни их соответствующих объектов. Вот почему вы должны быть осторожны, когда вы сохраняете лямбду в области, отличной от той, в которой она была определена.

См. пример проблем, связанных с lambda и его контекстным контекстом в этой разрешенной проблеме. Проверьте исправление, чтобы увидеть, что было сделано, чтобы сделать сохраненные лямбды безопасными для контекста.