【问题标题】:Lambda-Over-Lambda in C++14C++14 中的 Lambda-Over-Lambda
【发布时间】:2014-10-26 10:56:11
【问题描述】:

以下递归 lambda 调用如何结束/终止?

#include <cstdio>

auto terminal = [](auto term)            // <---------+  
{                                        //           |
    return [=] (auto func)               //           |  ???
    {                                    //           |
        return terminal(func(term));     // >---------+
    };
};


auto main() -> int
{
    auto hello =[](auto s){ fprintf(s,"Hello\n"); return s; };
    auto world =[](auto s){ fprintf(s,"World\n"); return s; };


    terminal(stdout)
            (hello)
            (world) ;

    return 0;

}

我在这里错过了什么?

Running code

【问题讨论】:

  • 这个列表不错的一个:gnu.org/fun/jokes/helloworld.html
  • 其实我对这个(是/可以)如何调用很感兴趣,所以我发布了一个后续问题:stackoverflow.com/questions/25619769/…
  • this question 的重复?
  • 也许您打算使用return terminal(func)(term); 而不是return terminal(func(term));?请注意,terminal 在执行任何操作之前需要使用参数 两次 调用。
  • 天哪,这:“auto main() -> int”太糟糕了。当旧工具已经非常适合这项工作时,尝试使用新工具并不有趣。还是“int main()”是 2010 年?

标签: c++ lambda c++14


【解决方案1】:

不是递归函数调用,一步一步看:

  1. terminal(stdout) - 这只是返回一个捕获 stdout 的 lambda
  2. 1. 的结果被 lambda hello 调用,它执行 lambda (func(term)),其结果被传递给 terminal(),它只返回一个 lambda,如 1 中一样。
  3. 2. 的结果用 lambda world 调用,和 2 一样,这次返回值被丢弃...

【讨论】:

    【解决方案2】:

    调用本身不是递归的。它返回一个函数对象,如果被调用,将再次调用terminal 以生成另一个函数对象。

    所以terminal(stdout) 返回一个函子,它捕获stdout 并且可以用另一个函数对象调用。再次调用它,(hello),使用捕获的术语stdout 调用hello 函子,输出"Hello";调用terminal 并返回另一个函子,这一次捕获hello 的返回值——仍然是stdout。调用该函子(world),同样如此,输出"World"

    【讨论】:

    • 谢谢 我正在浏览一篇文章,其中作者使用了两个不同的 lambda,但是,我在不知不觉中尝试将其包装在原始版本上。发现它有点令人困惑。感谢您的澄清。
    【解决方案3】:

    这里的关键是要明白这是有效的:

    world(hello(stdout));
    

    并将打印“Hello World”。 lambda 的递归系列可以展开为

    #include <cstdio>
    
    auto terminal = [](auto term)            // <---------+  
    {                                        //           |
        return [=] (auto func)               //           |  ???
        {                                    //           |
            return terminal(func(term));     // >---------+
        };
    };
    
    /*
    terminal(stdout) -returns> anonymous_lambda which captures stdout (functor)
    anonymous_lambda(hello) is called, func(term) is hello(stdout) and prints "Hello" and returns stdout, the anonymous_lambda -returns> terminal(stdout)
    (the above 2 lines start again)
    terminal(stdout) is called and -returns> anonymous_lambda which captures stdout (functor)
    anonymous_lambda(world) is called, func(term) is world(stdout) and prints "World" and returns stdout, the anonymous_lambda -returns> terminal(stdout)
    terminal(stdout) is called and -returns> anonymous_lambda which captures stdout (functor)
    nobody uses that anonymous_lambda.. end.
    */
    
    auto main() -> int
    {
        auto hello =[](auto s){ fprintf(s,"Hello\n"); return s; };
        auto world =[](auto s){ fprintf(s,"World\n"); return s; };
    
        world(hello(stdout));
    
    
        terminal(stdout)
                (hello)
                (world) ;
    
        return 0;
    
    }
    

    Coliru example

    【讨论】:

      【解决方案4】:

      它可以在内部翻译成如下所示:

      #include <cstdio>
      
      template <typename T>
      struct unnamed_lambda
      {
          unnamed_lambda(T term) : captured_term(term) {}
      
          template <typename A>
          unnamed_lambda operator()(A func);
      
          T captured_term;
      };
      
      struct terminal_lambda
      {
          template <typename A>
          unnamed_lambda<A> operator()(A term)
          {
              return unnamed_lambda<A>{term};
          }
      };
      
      terminal_lambda terminal;
      
      template <typename T>
      template <typename A>
      unnamed_lambda<T> unnamed_lambda<T>::operator()(A func)
      {
          return terminal(func(captured_term));
      }
      
      struct Hello
      {
          FILE* operator()(FILE* s)
          {
              fprintf(s, "Hello\n");
              return s;
          }
      };
      
      struct World
      {
          FILE* operator()(FILE* s)
          {
              fprintf(s, "World\n");
              return s;
          }
      };
      
      int main()
      {    
          Hello hello;
          World world;
          unnamed_lambda<FILE*> l1 = terminal(stdout);
          unnamed_lambda<FILE*> l2 = l1(hello);
          unnamed_lambda<FILE*> l3 = l2(world);
      
          // same as:
          terminal(stdout)(hello)(world);
      }
      

      LIVE DEMO

      实际上,这就是编译器在幕后使用 lambdas 所做的事情(有一些近似值)。

      【讨论】:

      • 正确(技术上令人印象深刻),但与 OP 提出的问题无关。
      【解决方案5】:

      我认为混淆的根源在于将 lambda 声明读取为 lambda 调用。确实在这里:

      auto terminal = [](auto term)            // <---------+  
      {                                        //           |
          return [=] (auto func)               //           |  ???
          {                                    //           |
              return terminal(func(term));     // >---------+
          };
      };
      

      作者刚刚声明了一个 lambda terminal,它接受一个任意参数 term 并返回一个未命名的 lambda,仅此而已!我们来看看这个未命名的 lambda,它:

      • 接受可调用对象func 作为参数并在复制捕获的参数term 上调用它和
      • 返回调用终端的结果,调用结果为func(term);所以它返回另一个未命名的 lambda,它捕获了 func(term) 的结果,但是这个 lambda 现在没有被调用,没有递归。

      现在主要的技巧应该更清楚了:

      1. terminal(stdout) 返回一个未命名的 lambda,它已捕获标准输出。
      2. (hello) 调用这个未命名的 lambda,作为 arg 传递 hello 可调用对象。这会在先前捕获的标准输出上调用。 hello(stdout) 再次返回标准输出,用作终端调用的参数,返回另一个已捕获标准输出的未命名 lambda。
      3. (world)同2。

      【讨论】:

        【解决方案6】:
        1. terminal(stdout) 返回一个函数,我们称它为函数x,参数为func。所以:

          terminal(stdout) ==&gt; x(func) { return terminal(func(stdout)) };

        2. 现在终端(stdout)(hello)调用函数x(hello)

          terminal(stdout)(hello) ==&gt; x(hello) { return terminal(hello(stdout)) };

          这导致hello 函数被调用并再次返回函数x

        3. 现在终端(std)(hello)(world)调用函数x(world)

          terminal(stdout)(hello) ==&gt; x(world) { return terminal(world(stdout)) };

          这会导致调用world 函数并再次返回函数x。函数x 现在不再调用,因为没有更多参数了。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-08-07
          • 1970-01-01
          • 1970-01-01
          • 2016-01-30
          • 1970-01-01
          • 2013-06-18
          相关资源
          最近更新 更多