Каково соглашение о вызовах по умолчанию для лямбда-функции C ++?

Следующий код был скомпилирован с 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
}

Я думаю, что ошибки нормальны, но все в порядке.

Итак, мои вопросы:

  1. Что такое соглашение о вызовах лямбда-функции C ++?

  2. Как указать соглашение о вызовах лямбда-функции C ++?

  3. Если соглашение о вызовах не определено, как правильно перезапустить пространство стека после вызова лямбда-функции?

  4. Компилятор автоматически генерирует несколько версий лямбда-функции? то есть как следующий псевдокод:

    [] __stdcall () {};

    [] __cdecl () {}; и т.п.

16

Решение

На VC ++ 2012 компилятор выбирает автоматически вызывающее преобразование для лямбд без сохранения состояния (у которых нет переменных захвата) когда вы конвертируете «лямбда без сохранения состояния в указатель на функцию».

MSDN C ++ 11 Особенности:

Лямбда

[…] Кроме того, в 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;
14

Другие решения

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

Стандарт 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 знает, какое соглашение о вызовах запрашивается. А при совершенной пересылке вышесказанное может быть даже эффективным.

3