Можно ли включить функцию лямбда в указатель функции?

У меня есть этот код:

void foo(void (*bar)()) {
    bar();
}

int main() {
    foo([] {
        int x = 2;
    });
}

Однако я волнуюсь, что это постигнет ту же участь, что:

struct X { int i; };

void foo(X* x) {
    x->i = 2;
}

int main() {
    foo(&X());
}

Что берет адрес локальной переменной.

Является ли первый пример полностью безопасным?

Ответ 1

Лямбда, которая ничего не фиксирует, неявно конвертируется в указатель функции с тем же списком аргументов и возвращаемым типом. Это могут сделать только безъядерные лямбды; если он захватывает что-либо, тогда они не могут.

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

Ответ 2

В дополнение к Nicol совершенно правильный общий ответ, я бы добавил некоторые взгляды на ваши особые страхи:

Однако я беспокоюсь, что это постигнет ту же судьбу, что и..., которая берет адрес локальной переменной.

Конечно, это так, но это абсолютно не проблема, когда вы просто вызываете его внутри foo (точно так же, как ваш пример структуры отлично работает), так как окружающая функция (main в этом случае), которая определена локальная переменная /lambda в любом случае переживет вызываемую функцию (foo). Это может быть только проблемой, если вы будете безопасно использовать эту локальную переменную или лямбда-указатель для последующего использования. Так

Является ли первый пример полностью безопасным?

Да, это так, как и второй пример.

Ответ 3

Да. Я считаю, что первый пример безопасен, независимо от времени жизни всех времен, созданных во время оценки полного выражения, которое включает в себя нечеткое лямбда-выражение.

За рабочий чертеж (n3485) 5.1.2 [expr.prim.lambda] p6

Тип замыкания для лямбда-выражения без лямбда-захвата имеет публичная не виртуальная неявная функция преобразования const в указатель для работы с теми же параметрами и типами возврата, что и закрытие type вызывает вызов функции. Значение, возвращаемое этим преобразованием функция должна быть адресом функции, которая при вызове имеет тот же эффект, что и вызов оператора вызова функции замыкания.

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

Например, я ожидаю, что следующее будет работать:

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

int foo( int (*sum)(int, int) ) { return sum(3, 4); }


int main() {
  foo( L() );
}

В то время как детали реализации clang, конечно, не являются окончательным словом на С++ (стандарт), если это заставляет вас чувствовать себя лучше, способ, которым это реализуется в clang, состоит в том, что когда выражение лямбда анализируется и анализируется семантически, закрытие -тип для выражения лямбда, и статическая функция добавляется в класс с семантикой, аналогичной оператору вызова функции лямбда. Поэтому, хотя срок жизни объекта лямбда, возвращаемого "L()", находится внутри тела "foo", преобразование в указатель к функции возвращает адрес статической функции, которая все еще действительна.

Рассмотрим несколько аналогичный случай:

struct B {
   static int f(int, int) { return 0; }
   typedef int (*fp_t)(int, int);
   operator fp_t() const { return &f; }
};
int main() {
  int (*fp)(int, int) = B{};
  fp(3, 4); // You would expect this to be ok.
}

Я, конечно, не эксперт по core-С++, но FWIW, это моя интерпретация буквы стандарта, и я считаю, что она защищаема.

Надеюсь, что это поможет.