从直观的角度来看,不需要携带任何状态(通过引用或其他方式)的lambda应该可以干净地转换为裸函数指针。但是,最近令我惊讶的是,在GCC,Clang和MSVC中出现以下失败:
int main(int, char *[]) {
void (*fp)() = []{}; // OK
//fp = [=]{}; // XXX - no user defined conversion operator available
//fp = [&]{}; // XXX - same ...
}
C ++ 17规范(或至少为visible public draft version N4713)在§8.4.5.1的第7条中 [expr.prim.lambda.closure] 指代有和没有的lambda捕获:
不具有lambda-capture 且其约束(如果有)满足的非泛型lambda表达式的闭包类型具有指向具有C ++语言链接(10.5)的函数的指针的转换功能与闭包类型的函数调用运算符相同的参数和返回类型。 ...
但是,查看正式语法,您可以在第8.4.5节 [expr.prim.lambda] 中看到以下内容:
- lambda-表达式 :
- lambda-introducer 复合语句
- ...
- lambda-介绍人 :
- [ lambda-capture opt ]
- ...
以及第8.4.5.2节 [expr.prim.lambda.capture] :
- lambda-capture :
- 默认捕获
- 捕获列表
- 捕获默认值,捕获列表
- 默认捕获 :
- &
- =
所以所有的编译器实际上都服从法律的规定,这让我感到沮丧...
为什么语言在声明中将捕获的存在定义为狭义的语法区别,而不是基于主体是否包含对任何非静态/捕获状态的引用?
答案 0 :(得分:11)
允许进行转换的更改是由国家机构的评论发起的。请参阅n3052: Converting Lambdas to Function Pointers,其中涉及国家机构comment UK 42:
具有空捕获列表的lambda具有与常规函数类型相同的语义。通过要求此映射,我们获得了具有已知API的高效lambda类型,该API也与现有操作系统和C库函数兼容。
,来自N3052
的分辨率为:
解决方案:添加新的一段:“具有空捕获集的lambda表达式应可转换为指向函数类型R(P)的指针,其中R是返回类型,P是lambda的parameter-type-list表达。”另外,最好(a)允许转换为函数引用,并且(b)允许外部“ C”函数指针类型。
...
在第5段之后添加一个新段落。此编辑的目的是为没有lambda捕获的lambda获得闭包到函数指针的转换。
没有lambda-capture的lambda表达式的闭包类型具有指向该函数的指针的公共非虚拟非显式const转换函数,该函数具有与闭包类型的函数调用操作符相同的参数和返回类型。该转换函数返回的值应该是一个函数的地址,该地址在被调用时与调用闭包类型的函数调用操作符具有相同的作用。
这就是我们今天的位置。请注意,注释中的空捕获列表和我们今天的内容似乎与注释中的措辞相符。
这似乎是基于国家机构评论的修正,并且适用范围狭窄。
答案 1 :(得分:2)
您提出的规则将非常脆弱,尤其是在P0588R1之前的世界中,隐式捕获取决于odr的使用。
考虑:
void g(int);
void h(const int&);
int my_min(int, int);
void f(int i) {
const int x = 1, y = i;
[=]{ g(x); }; // no capture, can convert?
[=]{ g(y); }; // captures y
[=]{ h(x); }; // captures x
[=]{ my_min(x, 0); }; // no capture, can convert?
[=]{ std::min(x, 0); }; // captures x
}