У меня такой код:

#include <functional>

//...

typedef int (*Myfun)(int);
std::function<int (int)> fn0([](int a)->int {
    return -a;
});
std::cout << "val == " << fn0(3) << std::endl; //"val == -3"
Myfun *fptr = fn0.target<Myfun>(); //fptr is NULL!!
std::cout << "val == " << (*fptr)(3) << std::endl; //Access violation error

Фактически, это код из MSDN с небольшим изменением: использование лямбда вместо простой функции.

Почему вызов fn0.target<Myfun>() возвращает NULL?

Когда я объявляю обычную функцию

int neg(int val) { 
    return (-val); 
}

И напишите std::function<int (int)> fn0(neg);, вроде все работает, но лямбда обрабатывается некорректно.

0
Cruel_Crow 2 Май 2014 в 22:28

2 ответа

Лучший ответ

Тип Myfun из typedef int (*Myfun)(int); не связан с типом целевого объекта функции, который является уникальным безымянным типом закрывающего объекта, созданного при выполнении выражения [](int a)->int { return -a; }

Попробуйте выполнить std::cout << fn0.target_type().name() << '\n';, чтобы убедиться в этом сами.

Когда вы объявляете функцию с помощью int neg(int val) { return (-val); }, тип neg точно равен Myfun (после преобразования функции в указатель, которое происходит в std::function<int(int)> fn0(neg)), поэтому std::function::target() может вернуть на него указатель.

5
Cubbi 2 Май 2014 в 18:39

Кубби объяснил, почему ваш код не работает - лямбда - это не указатель на функцию.

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

template<typename F>
struct as_pointer_t {
  F f;
  template<typename R, typename... Args>
  operator type<R(*)(Args...)>() const { return {f}; }
  template<typename R, typename... Args>
  operator std::function<R(Args...)>() const { return (R(*)(Args...))f; }
};
template<typename F>
as_pointer_t<F> as_pointer( F&& f ) { return {std::forward<F>(f)}; }

Теперь мы можем это сделать:

int main() {
  typedef int (*Myfun)(int);
  std::function<int (int)> fn0(as_pointer([](int a)->int {
    return -a;
  }));
  std::cout << "val == " << fn0(3) << std::endl; //"val == -3"
  Myfun *fptr = fn0.target<Myfun>(); //fptr is no longer NULL!!
  std::cout << "val == " << (*fptr)(3) << std::endl;
}

И ваш код работает так, как ожидалось. Однако вышеуказанное компилируется только в том случае, если ваша лямбда ничего не улавливает.

Если ваша цель - преобразовать захватывающую лямбду в указатель на функцию, вы не можете этого сделать. Вы можете сохранить состояние в глобальной переменной и использовать его в лямбде без захвата. Вы также можете преобразовать захватывающую лямбду в пару указателя на функцию и void*.

Я написал код, который использует индекс времени компиляции для вставки void* в список (и дополнительный тип для использования вместо void*) и производит указанную пару void* и функцию указатель. Общий случай сложен - конкретный случай (скажем, первый аргумент) намного проще.

template<typename T> using type=T;

template<typename F, typename X=void*>
struct callback_t {
  F f;
  operator X() { return X(&f); }
  template<typename R, typename...Args>
  operator type<R(*)(X, Args...)>() const {
    return []( X x, Args... args )->R {
      F* f = (F*)(x);
      return (*f)(std::forward<Args>(args)...);
    };
  }
};
template<typename X=void*, typename F>
callback_t<F,X> make_callback( F f ) {
  return {std::forward<F>(f)};
}

Использование:

typedef void(*pfun)(void*, int);
void call_pfun( pfun f, void* p) {
  for (int i = 0; i < 3; ++i)
    f( p, i );
}
int main()
{
  int y = 7;
  auto callback = make_callback([y]( int x ) { std::cout << x+y << "\n"; });
  call_pfun( callback, callback );
}

живой пример.

1
Yakk - Adam Nevraumont 2 Май 2014 в 20:35