Время жизни лямбда-объектов относительно преобразования указателя на функцию - PullRequest
36 голосов
/ 06 ноября 2011

После этого ответа Теперь мне интересно, каковы правила для времени жизни лямбда-выражения и как они соотносятся со временем жизни указателей на функции, которые создаются автоматическим преобразованием.Есть несколько вопросов о времени жизни лямбд (например, здесь и здесь ), и в этом случае ответы «они ведут себя точно так же, как вы сами написали полный объект функтора», однако ниобратимся к преобразованию в указатель на функцию, что вполне разумно может быть особым случаем.

Я собрал небольшой рабочий пример, иллюстрирующий мое беспокойство:

#include <iostream>

typedef int (*func_t)(int);

// first case
func_t retFun1() {
  static auto lambda = [](int) { return 1; };
  // automatically converted to func_t
  return lambda;
}

// second case
func_t retFun2() {
  // no static
  auto lambda = [](int) { return 2; };
  // automatically converted to func_t and 
  // the local variable lambda reaches the end of its life
  return lambda;
}

int main() {
  const int a = retFun1()(0);
  const int b = retFun2()(0);
  std::cout << a << "," << b << std::endl;
  return 0;
}

Хорошо ли это определено для обоих случаев?Или только для retFun1()?Вопрос заключается в том, «является ли та функция, на которую указывает указатель функции, необходимой для вызова самого объекта функтора или для переопределения тела в отдельной функции?»Любой из них может иметь смысл, но тот факт, что преобразование в указатель на функцию, в частности, требует лямбда без захвата, наводит на мысль, что на самом деле это может быть последний.


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

func_t retFun3() {
  struct __voodoo_magic_lambda_implementation {
    int operator()(int) const {
      return 3;
    }
    static int plainfunction(int) {
      return 3;
    }
    operator func_t() const {
      return plainfunction;
    }
  } lambda;
  return lambda;
}

, в этом случае подойдут как static, так и не static варианты retFun.Однако, если для компилятора также допустимо реализовывать лямбда-выражения, например:

static int __voodoo_impl_function(int x);
static struct __voodoo_maigc_impl2 {
  int operator()(int) const {
    return 4;
  }
  operator func_t() const {
    return __voodoo_impl_function;
  }
} *__magic_functor_ptr;
static int __voodoo_impl_function(int x) {
  return (*__magic_functor_ptr)(x);
}

func_t retFun4() {
  __voodoo_maigc_impl2 lambda;
  // non-static, local lifetime
  __magic_functor_ptr = &lambda; //Or do the equivalent of this in the ctor
  return lambda;
}

, тогда retFun2() - неопределенное поведение.

1 Ответ

20 голосов
/ 06 ноября 2011

§5.1.2 / 6 говорит:

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

Выделить мое.

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

Это немного похоже на то, как если бы вы сделали:

func_t retFun2()
{
    int __lambda0(int)
    {
        return 2;
    }

    struct
    {
        int operator(int __arg0) const
        {
            return __lambda0(__arg0);
        }

        operator decltype(__lambda0)() const
        {
            return __lambda0;
        }
    } lambda;

    return lambda; // just the address of a regular ol' function
}
...