16

以下代码使用 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
}

我认为错误是正常的,但 OK 是异常的。

所以,我的问题是:

  1. C++ lambda 函数的调用约定是什么?

  2. 如何指定 C++ lambda 函数的调用约定?

  3. 如果未定义调用约定,如何在调用 lambda 函数后正确回收堆栈空间?

  4. 编译器会自动生成多个版本的 lambda 函数吗?即如下伪代码:

    [] __stdcall (){};

    [] __cdecl (){}; 等等

4

2 回答 2

15

在 VC++ 2012 上,当您将“无状态 lambda 转换为函数指针”时,编译器会选择自动调用无状态 lambda(没有捕获变量)的转换。

MSDN C++11 特点

拉姆达斯

[...] 此外,在 Visual Studio 2012 的 Visual C++ 中,无状态 lambda 可转换为函数指针。[...](Visual Studio 2012 中的 Visual C++ 甚至比这更好,因为我们已将无状态 lambda 转换为具有任意调用约定的函数指针。当您使用期望__stdcall函数指针之类的 API 时,这一点很重要.)


编辑:

注意:调用转换超出了 C++ 标准,它依赖于其他规范,例如平台 ABI(应用程序二进制接口)。

以下答案基于带有/FAs 编译器选项的输出汇编代码。所以这只是一个猜测,请向微软询问更多细节;P

Q1。C++ lambda 函数的调用约定是什么?

Q3。如果未定义调用约定,如何在调用 lambda 函数后正确回收堆栈空间?

首先,C++ lambda(-expression)不是函数(也不是函数指针),您可以operator()像调用普通函数一样调用 lambda 对象。并且输出汇编代码说 VC++ 2012 生成带有__thiscall调用转换的 lambda-body。

Q2。如何指定 C++ lambda 函数的调用约定?

AFAIK,没有办法。(可能只有__thiscall

Q4。编译器会自动生成多个版本的 lambda 函数吗?即作为以下伪代码:[...]

可能不会。VC++ 2012 lambda-type 仅提供一个 lambda-body 实现 ( 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;
于 2013-02-13T03:41:54.027 回答
3

无状态的 lambda 函数仍然是一个类,但是一个可以隐式转换为函数指针的类。

C++ 标准不涵盖调用约定,但是当 lambda 转换为函数指针时,无状态 lambda 无法在转发到无状态 lambda 的任何调用约定中创建包装器几乎没有理由。

例如,我们可以这样做:

#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无状态空值类可以隐式转换为 acdeclstdcall函数指针。

重要的部分是调用约定是函数指针类型的一部分,因此operator function_type知道正在请求什么调用约定。并且通过完美的转发,以上甚至可以是高效的。

于 2013-02-13T03:43:36.580 回答