C++ 兰姆达:这合法吗?
考虑一下这个相当无用的程序:C++ 兰姆达:这合法吗?,c++,lambda,language-lawyer,c++17,auto,C++,Lambda,Language Lawyer,C++17,Auto,考虑一下这个相当无用的程序: #include <iostream> int main(int argc, char* argv[]) { int a = 5; auto it = [&](auto self) { return [&](auto b) { std::cout << (a + b) << std::endl; return self(self); }; };
#include <iostream>
int main(int argc, char* argv[]) {
int a = 5;
auto it = [&](auto self) {
return [&](auto b) {
std::cout << (a + b) << std::endl;
return self(self);
};
};
it(it)(4)(6)(42)(77)(999);
}
#包括
int main(int argc,char*argv[]){
INTA=5;
自动it=[&](自动自我){
返回[&](自动b){
STD::CUT< P> <强>编辑< /强>:对于C++的规范是否严格有效,似乎存在争议。主流观点似乎是无效的。请参阅其他答案以进行更深入的讨论。下面的代码可以与MSVC++和gcc一起使用,OP发布了进一步修改的代码,也可以与clang一起使用
这是未定义的行为,因为内部lambda通过引用捕获参数self
,但是self
在第7行的return
之后超出范围。因此,当稍后执行返回的lambda时,它正在访问对超出范围的变量的引用
#include <iostream>
int main(int argc, char* argv[]) {
int a = 5;
auto it = [&](auto self) {
return [&](auto b) {
std::cout << (a + b) << std::endl;
return self(self); // <-- using reference to 'self'
};
};
it(it)(4)(6)(42)(77)(999); // <-- 'self' is now out of scope
}
相反,您可以将外部lambda更改为通过引用而不是通过值获取self,从而避免了一堆不必要的副本,并解决了以下问题:
#include <iostream>
int main(int argc, char* argv[]) {
int a = 5;
auto it = [&](auto& self) { // <-- self is now a reference
return [&](auto b) {
std::cout << (a + b) << std::endl;
return self(self);
};
};
it(it)(4)(6)(42)(77)(999);
}
似乎CLAN是正确的。考虑一个简化的例子:
auto it = [](auto& self) {
return [&self]() {
return self(self);
};
};
it(it);
让我们像编译器一样(稍微)检查一下:
it
的类型是带有模板调用操作符的Lambda1
it(it);
触发呼叫操作符的实例化
- 模板调用操作符的返回类型是
auto
,因此我们必须推导它
- 我们返回一个lambda,它捕获类型为
Lambda1
的第一个参数
- lambda也有一个call操作符,它返回调用的类型
self(self)
- 注意:
self(self)
正是我们开始使用的
因此,无法推断类型。程序格式不正确(clang是正确的):
如果表达式中出现具有未还原占位符类型的实体名称,则程序的格式不正确。但是,一旦在函数中看到未丢弃的返回语句,则从该语句推导出的返回类型可用于函数的其余部分,包括其他返回语句
基本上,内部lambda的返回类型的推导取决于它自身(这里命名的实体是call操作符)-因此必须显式提供返回类型。在这种情况下,这是不可能的,因为您需要内部lambda的类型,但无法命名它。但在其他情况下,尝试像这样强制递归lambda是可行的
即使没有这个,你也有一个梦想
在与更聪明的人(即T.C.)讨论后,让我进一步阐述一下,原始代码(稍微减少)和建议的新版本(同样减少)之间有一个重要区别:
也就是说,内部表达式self(self)
不依赖于f1
,但self(self,p)
依赖于f2
。当表达式不依赖时,它们可以被急切地使用(,例如,howstatic_assert(false)
是一个硬错误,无论其所在的模板是否实例化)
对于f1
,编译器(比如说,clang)可以急切地尝试实例化它。当您在上面的#2
点(它是内部lambda的类型)到达该#2>点时,您就知道外部lambda的推断类型,但我们尝试更早地使用它(将其视为#1
)-我们试图在解析内部lambda时使用它,但我们还不知道它的实际类型。这与dcl.spec.auto/9相冲突
然而,对于f2
,我们不能急切地尝试实例化,因为它是依赖的。我们只能在使用点实例化,在使用点我们知道一切
为了真正做到这一点,您需要一个。论文中的实现:
模板
类y_组合子_结果{
乐趣;;
公众:
模板
显式y_组合器_结果(T&&fun):fun_(std::forward(fun)){
模板
decltype(自动)运算符()(Args&&…Args){
返回(std::ref(*this),std::forward(args)…);
}
};
模板
decltype(自动)y_组合器(乐趣和乐趣){
返回y_组合器_结果(std::forward(fun));
}
你想要的是:
auto it = y_combinator([&](auto self, auto b){
std::cout << (a + b) << std::endl;
return self;
});
auto-it=y_组合器([&](auto-self,auto-b){
病人太长了,读不下去了;
叮当声是正确的
看起来标准中导致这种格式错误的部分是:
如果表达式中出现具有未减少占位符类型的实体名称,则程序将被删除
格式错误。一旦在函数中看到未丢弃的返回语句,返回类型
从该语句推导的表达式可用于函数的其余部分,包括其他返回语句。
[示例:
auto n = n; // error, n’s initializer refers to n
auto f();
void g() { &f; } // error, f’s return type is unknown
auto sum(int i) {
if (i == 1)
return i; // sum’s return type is int
else
return sum(i-1)+i; // OK, sum’s return type has been deduced
}
-[结束示例]
原著
如果我们看一下提案,它提供了一个有效的解决方案:
template<class Fun>
class y_combinator_result {
Fun fun_;
public:
template<class T>
explicit y_combinator_result(T &&fun): fun_(std::forward<T>(fun)) {}
template<class ...Args>
decltype(auto) operator()(Args &&...args) {
return fun_(std::ref(*this), std::forward<Args>(args)...);
}
};
template<class Fun>
decltype(auto) y_combinator(Fun &&fun) {
return y_combinator_result<std::decay_t<Fun>>(std::forward<Fun>(fun));
}
这里,“fib”相当于lambda的*this(尽管lambda的闭包类型不完整,但有一些恼人的特殊规则允许它工作)。
Barry向我指出了后续提案,该提案解释了为什么这是不可能的,并围绕着dcl.spec.auto#9
限制进行了工作,还展示了在没有该限制的情况下实现这一点的方法:
LAMBDAS是一种有用的局部代码重构工具,但是,有时我们希望使用内部的lambda,允许直接递归或允许闭包作为延续。这在当前的C++中是很难实现的。
例如:
void read(Socket sock, OutputBuffer buff) {
sock.readsome([&] (Data data) {
buff.append(data);
sock.readsome(/*current lambda*/);
}).get();
}
从自身引用lambda的一种自然尝试是将其存储在变量中,并通过引用捕获该变量:
auto on_read = [&] (Data data) {
buff.append(data);
sock.readsome(on_read);
};
auto it = y_combinator([&](auto self, auto b){
std::cout << (a + b) << std::endl;
return self;
});
auto n = n; // error, n’s initializer refers to n
auto f();
void g() { &f; } // error, f’s return type is unknown
auto sum(int i) {
if (i == 1)
return i; // sum’s return type is int
else
return sum(i-1)+i; // OK, sum’s return type has been deduced
}
template<class Fun>
class y_combinator_result {
Fun fun_;
public:
template<class T>
explicit y_combinator_result(T &&fun): fun_(std::forward<T>(fun)) {}
template<class ...Args>
decltype(auto) operator()(Args &&...args) {
return fun_(std::ref(*this), std::forward<Args>(args)...);
}
};
template<class Fun>
decltype(auto) y_combinator(Fun &&fun) {
return y_combinator_result<std::decay_t<Fun>>(std::forward<Fun>(fun));
}
auto x = []fib(int a) { return a > 1 ? fib(a - 1) + fib(a - 2) : a; };
void read(Socket sock, OutputBuffer buff) {
sock.readsome([&] (Data data) {
buff.append(data);
sock.readsome(/*current lambda*/);
}).get();
auto on_read = [&] (Data data) {
buff.append(data);
sock.readsome(on_read);
};
std::function on_read = [&] (Data data) {
buff.append(data);
sock.readsome(on_read);
};
#include <iostream>
struct Outer
{
int& a;
// Actually a templated argument, but always called with `Outer`.
template< class Arg >
auto operator()( Arg& self ) const
//-> Inner
{
return Inner( a, self ); //! Original code has dangling ref here.
}
struct Inner
{
int& a;
Outer& self;
// Actually a templated argument, but always called with `int`.
template< class Arg >
auto operator()( Arg b ) const
//-> Inner
{
std::cout << (a + b) << std::endl;
return self( self );
}
Inner( int& an_a, Outer& a_self ): a( an_a ), self( a_self ) {}
};
Outer( int& ref ): a( ref ) {}
};
int main() {
int a = 5;
auto&& it = Outer( a );
it(it)(4)(6)(42)(77)(999);
}
#include <iostream>
struct Outer
{
int& a;
template< class > class Inner;
// Actually a templated argument, but always called with `Outer`.
template< class Arg >
auto operator()( Arg& self ) const
//-> Inner
{
return Inner<Arg>( a, self ); //! Original code has dangling ref here.
}
template< class Self >
struct Inner
{
int& a;
Self& self;
// Actually a templated argument, but always called with `int`.
template< class Arg >
auto operator()( Arg b ) const
//-> Inner
{
std::cout << (a + b) << std::endl;
return self( self );
}
Inner( int& an_a, Self& a_self ): a( an_a ), self( a_self ) {}
};
Outer( int& ref ): a( ref ) {}
};
int main() {
int a = 5;
auto&& it = Outer( a );
it(it)(4)(6)(42)(77)(999);
}
template<class F>
struct ycombinator {
F f;
template<class...Args>
auto operator()(Args&&...args){
return f(f, std::forward<Args>(args)...);
}
};
template<class F>
ycombinator(F) -> ycombinator<F>;
ycombinator bob = {[x=0](auto&& self)mutable{
std::cout << ++x << "\n";
ycombinator ret = {self};
return ret;
}};
bob()()(); // prints 1 2 3
auto it = [&](auto self) { // outer
return [&](auto b) { // inner
std::cout << (a + b) << std::endl;
return self(self);
};
};
it(it)(4)(5)(6);
[&](auto self) {
return [self,&a](auto b) {
std::cout << (a + b) << std::endl;
return self(self);
};
};
struct __outer_lambda__ {
template<class T>
auto operator()(T self) const {
struct __inner_lambda__ {
template<class B>
auto operator()(B b) const {
std::cout << (a + b) << std::endl;
return self(self);
}
int& a;
T self;
};
return __inner_lambda__{a, self};
}
int& a;
};
__outer_lambda__ it{a};
it(it);
template<>
auto __outer_lambda__::operator()(__outer_lambda__ self) const {
struct __inner_lambda__ {
template<class B>
auto operator()(B b) const {
std::cout << (a + b) << std::endl;
return self(self);
}
int& a;
__outer_lambda__ self;
};
return __inner_lambda__{a, self};
}
int& a;
};
struct __inner_lambda__ {
template<class B>
auto operator()(B b) const {
std::cout << (a + b) << std::endl;
return self(self);
}
int& a;
__outer_lambda__ self;
};
template<class A, class B>
struct second_type_helper { using result=B; };
template<class A, class B>
using second_type = typename second_type_helper<A,B>::result;
int main(int argc, char* argv[]) {
int a = 5;
auto it = [&](auto self) {
return [self,&a](auto b) {
std::cout << (a + b) << std::endl;
return self(second_type<decltype(b), decltype(self)&>(self) );
};
};
it(it)(4)(6)(42)(77)(999);
}