我从未见过这样的事;我似乎无法绕过它。这段代码甚至做了什么?它看起来非常华丽,我很确定这些东西在我的C书中没有任何描述。 :(
union u;
typedef union u (*funcptr)();
union u {
funcptr f;
int i;
};
typedef union u $;
int main() {
int printf(const char *, ...);
$ fact =
($){.f = ({
$ lambda($ n) {
return ($){.i = n.i == 0 ? 1 : n.i * fact.f(($){.i = n.i - 1}).i};
}
lambda;
})};
$ make_adder = ($){.f = ({
$ lambda($ n) {
return ($){.f = ({
$ lambda($ x) {
return ($){.i = n.i + x.i};
}
lambda;
})};
}
lambda;
})};
$ add1 = make_adder.f(($){.i = 1});
$ mul3 = ($){.f = ({
$ lambda($ n) { return ($){.i = n.i * 3}; }
lambda;
})};
$ compose = ($){
.f = ({
$ lambda($ f, $ g) {
return ($){.f = ({
$ lambda($ n) {
return ($){.i = f.f(($){.i = g.f(($){.i = n.i}).i}).i};
}
lambda;
})};
}
lambda;
})};
$ mul3add1 = compose.f(mul3, add1);
printf("%d\n", fact.f(($){.i = 5}).i);
printf("%d\n", mul3.f(($){.i = add1.f(($){.i = 10}).i}).i);
printf("%d\n", mul3add1.f(($){.i = 10}).i);
return 0;
}
答案 0 :(得分:5)
此示例主要基于两个GCC扩展:nested functions和statement expressions。
嵌套函数扩展允许您在另一个函数的主体内定义一个函数。应用常规块作用域规则,因此嵌套函数在调用时可以访问外部函数的局部变量:
void outer(int x) {
int inner(int y) {
return x + y;
}
return inner(6);
}
...
int z = outer(4)' // z == 10
语句表达式扩展允许您包装C块语句(您通常能够在大括号中放置的任何代码:变量声明,for
循环等)用于产生价值的环境中。它看起来像括号中的块语句:
int foo(x) {
return 5 + ({
int y = 0;
while (y < 10) ++y;
x + y;
});
}
...
int z = foo(6); // z == 20
包装块中的最后一个语句提供值。所以它的工作方式与您想象的内联函数体非常相似。
这两个组合使用的扩展允许您定义一个可以访问周围范围变量的函数体,并在表达式中立即使用它,创建一种基本的lambda表达式。由于语句表达式可以包含任何语句,并且嵌套函数定义是一个语句,而函数的名称是一个值,语句表达式可以定义一个函数并立即将指向该函数的指针返回给周围的表达:
int foo(int x) {
int (*f)(int) = ({ // statement expression
int nested(int y) { // statement 1: function definition
return x + y;
}
nested; // statement 2 (value-producing): function name
}); // f == nested
return f(6); // return nested(6) == return x + 6
}
示例中的代码通过使用美元符号作为返回类型(another GCC extension的缩写标识符来进一步修改它,对示例的功能更不重要)。示例中的lambda
不是关键字或宏(但美元应该看起来像一个),它只是在语句表达式范围内定义的函数名称(重复使用多次)。 C的范围嵌套规则意味着在更深的范围内重用相同的名称(嵌套的“lambdas”)是完全可以的,特别是当没有期望使用该名称的身体代码用于任何其他目的时(lambdas通常是匿名的,所以函数我们不会“知道”他们实际上是被称为 lambda
)。
如果你阅读了嵌套函数的GCC文档,你会发现这种技术非常有限。嵌套函数在其包含框架的生命周期结束时到期。这意味着它们无法返回,并且它们无法真正有效地存储。它们可以通过指针传递到从包含期望普通函数指针的包含帧调用的其他函数中,因此它们仍然非常有用。但它们没有任何接近真正lambda的灵活性,它们接受它们所关闭的变量的所有权(共享或总取决于语言),并且可以作为真值传递到所有方向或存储供以后使用完全不相关的程序部分。即使你把它包装在很多辅助宏中,语法也相当笨拙。
C很可能会在下一版本的语言中获得真正的lambdas,目前称为C2x。您可以阅读有关建议表单here的更多信息 - 它看起来并不像这样(它复制了Objective-C中的匿名函数语法和语义)。以这种方式创建的函数的生命周期可以超出其创建范围;函数体是真正的表达式,不需要包含语句的hack;并且函数本身是真正的匿名,不需要像lambda
这样的中间名称。
以上示例的C2x版本很可能看起来像这样:
#include <stdio.h>
int main(void) {
typedef int (^ F)(int);
__block F fact; // needs to be mutable - block can't copy-capture
// its own variable before initializing it
fact = ^(int n) {
return n == 0 ? 1 : n * fact(n - 1);
};
F (^ make_adder)(int) = ^(int n) {
return _Closure_copy(^(int x) { return n + x; });
};
F add1 = make_adder(1);
F mul3 = ^(int n) { return n * 3; };
F (^ compose)(F, F) = ^(F f, F g) {
return _Closure_copy(^(int n) { return f(g(n)); });
};
F mul3add1 = compose(mul3, add1);
printf("%d\n", fact(5));
printf("%d\n", mul3(add1(10)));
printf("%d\n", mul3add1(10));
_Closure_free(add1);
_Closure_free(mul3add1);
return 0;
}
没有所有联盟的东西就更简单了。
(您现在可以在Clang中编译并运行此修改后的示例 - 使用-fblocks
标志启用lambda扩展,将#include <Block.h>
添加到文件顶部,然后替换{{1} }和_Closure_copy
分别与_Closure_free
和Block_copy
。)