Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/160.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 兰姆达:这合法吗?_C++_Lambda_Language Lawyer_C++17_Auto - Fatal编程技术网

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
。当表达式不依赖时,它们可以被急切地使用(,例如,how
static_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);
}