Следующий код был скомпилирован с VC ++ 2012:
void f1(void (__stdcall *)())
{}
void f2(void (__cdecl *)())
{}
void __cdecl h1()
{}
void __stdcall h2()
{}
int main()
{
f1(h1); // error C2664
f2(h2); // error C2664
f1([](){}); // OK
f2([](){}); // OK
auto fn = [](){};
f1(fn); // OK
f2(fn); // OK
}
Я думаю, что ошибки нормальны, но все в порядке.
Итак, мои вопросы:
Что такое соглашение о вызовах лямбда-функции C ++?
Как указать соглашение о вызовах лямбда-функции C ++?
Если соглашение о вызовах не определено, как правильно перезапустить пространство стека после вызова лямбда-функции?
Компилятор автоматически генерирует несколько версий лямбда-функции? то есть как следующий псевдокод:
[] __stdcall () {}; [] __cdecl () {}; и т.п.На VC ++ 2012 компилятор выбирает автоматически вызывающее преобразование для лямбд без сохранения состояния (у которых нет переменных захвата) когда вы конвертируете «лямбда без сохранения состояния в указатель на функцию».
Лямбда
[…] Кроме того, в Visual C ++ в Visual Studio 2012 лямбды без сохранения состояния можно преобразовывать в указатели функций. […] (Visual C ++ в Visual Studio 2012 даже лучше, потому что мы сделали лямбда-выражения без состояния конвертируемыми в указатели функций, которые имеют произвольные соглашения о вызовах. Это важно, когда вы используете API, которые ожидают таких вещей, как__stdcall
функциональные указатели.)
Редакция:
NB: Преобразование вызова не соответствует стандарту C ++, оно зависит от других спецификаций, таких как платформа ABI (двоичный интерфейс приложения).
Следующие ответы основаны на выходном коде сборки с Опция компилятора / FAs.
Так что это всего лишь предположение, и, пожалуйста, обратитесь к Microsoft за более подробной информацией; P
Q1. Что такое соглашение о вызовах лямбда-функции C ++?
Q3. Если соглашение о вызовах не определено, как правильно перезапустить пространство стека после вызова лямбда-функции?
Прежде всего, C ++ лямбда (-expression) НЕ является функцией (ни указателем функции), вы можете вызвать operator()
лямбда-объект, как вызывающая нормальная функция.
И выходной код сборки говорит, что VC ++ 2012 генерирует лямбда-тело с __thiscall
вызов конверсии.
Q2. Как указать соглашение о вызовах лямбда-функции C ++?
AFAIK, нет пути. (Может быть только __thiscall
)
Q4. Компилятор автоматически генерирует несколько версий лямбда-функции? то есть как следующий псевдокод: […]
Вероятно, не.
Лямбда-тип VC ++ 2012 предоставляет только одну реализацию лямбда-тела (void operator()()
), но предоставляет несколько «определенных пользователем преобразований в указатель на функцию» для каждого вызывающего преобразования (оператор возвращает указатель на функцию с void (__fastcall*)(void)
, void (__stdcall*)(void)
, а также void (__cdecl*)(void)
тип).
Вот пример;
// input source code
auto lm = [](){ /*lambda-body*/ };
// reversed C++ code from VC++2012 output assembly code
class lambda_UNIQUE_HASH {
void __thiscall operator()() {
/* lambda-body */
}
// user-defined conversions
typedef void (__fastcall * fp_fastcall_t)();
typedef void (__stdcall * fp_stdcall_t)();
typedef void (__cdecl * fp_cdecl_t)();
operator fp_fastcall_t() { ... }
operator fp_stdcall_t() { ... }
operator fp_cdecl_t() { ... }
};
lambda_UNIQUE_HASH lm;
Лямбда-функция без сохранения состояния по-прежнему является классом, но классом, который можно неявно преобразовать в указатель на функцию.
Стандарт C ++ не распространяется на соглашения о вызовах, но есть небольшая причина, по которой лямбда без сохранения состояния не может создать оболочку в любом соглашении о вызовах, которая переходит к лямбде без сохранения состояния, когда лямбда преобразуется в указатель на функцию.
В качестве примера, мы могли бы сделать это:
#include <iostream>
void __cdecl h1() {}
void __stdcall h2(){}
// I'm lazy:
typedef decltype(&h1) cdecl_nullary_ptr;
typedef decltype(&h2) stdcall_nullary_ptr;
template<typename StatelessNullaryFunctor>
struct make_cdecl {
static void __cdecl do_it() {
StatelessNullaryFunctor()();
}
};
template<typename StatelessNullaryFunctor>
struct make_stdcall {
static void __stdcall do_it() {
StatelessNullaryFunctor()();
}
};
struct test {
void operator()() const { hidden_implementation(); }
operator cdecl_nullary_ptr() const {
return &make_cdecl<test>::do_it;
}
operator stdcall_nullary_ptr() const {
return &make_stdcall<test>::do_it;
}
};
где наш test
Нулевой класс без сохранения состояния может быть преобразован в cdecl
а также stdcall
указатель на функцию неявно.
Важной частью этого является то, что соглашение о вызовах является частью типа указателя функции, поэтому operator function_type
знает, какое соглашение о вызовах запрашивается. А при совершенной пересылке вышесказанное может быть даже эффективным.