【问题标题】:Generic Lambdas泛型 Lambda
【发布时间】:2023-04-02 00:07:01
【问题描述】:

这行代码(代码1)有什么区别

auto l1 = [](auto a) { static int l = 0; std::cout << a << " " << ++l << std::endl; };

这一行(代码 2)

static int l = 0;
auto l1 = [](auto a) {  std::cout << a << " " << ++l << std::endl; };

还有这个? (代码 3)

int l = 0;
auto l1 = [l](auto a) mutable {  std::cout << a << " " << ++l << std::endl; };

主要

l1("Joe");
l1("Joo");
l1(1.5);

有时 int 变量 'l' 在调用之间共享,有时则不是。对于 lambda 的参数之一具有 auto ,它会创建 lambda 的多个实例吗?我不完全确定(代码 1)与(代码 2)有何不同以及(代码 2)与(代码 3)有何不同。我期待(代码 3)创建 lambda 的多个实例,因此输出将是 (Joe 1, Joe 2, 1.5 1) 但结果是 (Joe 1, Joe 2, 1.5 3)。

【问题讨论】:

    标签: c++ lambda c++14 auto generic-lambda


    【解决方案1】:

    将其中一个 lambda 参数设为 auto,是否会创建多个 lambda 实例?

    它不是创建多个 lambda 实例,但 lambda 将具有模板化的 operator(),以及具有不同类型的多个实例化。

    对于第一种情况,当使用const char* 调用两次时,会打印出相同的static 变量l(在operator() 的实例化中为const char* 类型定义)。当使用double 调用时,会打印static 变量l(在operator() 的实例化中定义为double 类型),所以你会得到

    Joe 1
    Joo 2
    1.5 1
    

    对于第二种情况,它们都引用了从 lambda 定义的同一个变量 l,然后你会得到

    Joe 1
    Joo 2
    1.5 3
    

    对于第三种情况,它们都引用了 lambda 捕获的同一个变量 l,(甚至调用了 operator() 的不同实例化,)然后你会得到

    Joe 1
    Joo 2
    1.5 3
    

    【讨论】:

      【解决方案2】:

      在第一个示例中,静态变量的数量与 lambda 的实例数量一样多。其中可能有很多,因为您将参数指定为 auto,这使您的 lambda 成为某种模板。

      第三个代码在每次创建 lambda 时按值捕获l,并创建它的本地副本,因此根本不会共享任何内容。

      让我们看一个例子。让我们创建一个函数f(),其中定义了 lambda,并调用该函数两次:

      void f() {
          //auto l1 = ...
      
          l1(1);
          l1(2);
          l1(3);
          l1('a');
          l1('b');
          l1('c');
      }
      
      f();
      std::cout << "---" << std::endl;
      f();
      

      此代码使用 int 值调用相同的 lambda 3 次,使用 char 值调用 3 次。

      代码 1:l 在实例之间共享,因此我们会看到使用了 2 个不同的变量:

          1 1
          2 2
          3 3
          a 1
          b 2
          c 3
          ---
          1 4
          2 5
          3 6
          a 4
          b 5
          c 6
      

      f() 的第二次调用允许您重用静态变量,但对于两种不同的类型有两个不同的变量。

      代码 2:l 是唯一的静态变量,在所有 lambda 之间共享:

          1 1
          2 2
          3 3
          a 4
          b 5
          c 6
          ---
          1 7
          2 8
          3 9
          a 10
          b 11
          c 12
      

      代码 3:每个函数调用都会创建一个 lambda,并且此 lambda 使用在创建时捕获的变量 l 的唯一实例:

          1 1
          2 2
          3 3
          a 4
          b 5
          c 6
          ---
          1 1
          2 2
          3 3
          a 4
          b 5
          c 6
      

      如果您再次运行此代码,您会看到 l 再次重新创建,结果将重复。

      【讨论】:

        【解决方案3】:

        对于这个版本:

        auto l1 = [](auto a) { static int l = 0; std::cout << a << " " << ++l << std::endl; };
        

        lambda 的调用运算符有 2 个实例化,一个用于 2 个const char * 调用,一个用于double 调用。每个实例化有一个静态变量l,因此输出为:

        l1("Joe"); // Joe 1
        l1("Joo"); // Joo 2
        l1(1.5);   // 1.5 1
        

        对于这个版本:

        static int l = 0;
        auto l1 = [](auto a) {  std::cout << a << " " << ++l << std::endl; };
        

        只有一个静态变量l,所以对于lambda的调用操作符的所有实例化只有1个l,输出为:

        l1("Joe"); // Joe 1
        l1("Joo"); // Joo 2
        l1(1.5);   // 1.5 3
        

        第 3 版与第 2 版的效果相同,只是变量 l 需要在函数本地范围内声明。

        【讨论】:

          【解决方案4】:

          Lambda 基本上只是声明函子的一种简单方法。这段代码:

          #include <iostream>
          
          auto l1 = [](auto a) { static int l = 0; std::cout << a << " " << ++l << std::endl; };
          
          static int m = 0;
          auto l2 = [](auto a) {  std::cout << a << " " << ++m << std::endl; };
          
          int n = 0;
          auto l3 = [n](auto a) mutable {  std::cout << a << " " << ++n << std::endl; };
          
          int main(){
              l1("Joe");
              l1("Joo");
              l1(1.5);
              std::cout << "\n";
              l2("Joe");
              l2("Joo");
              l2(1.5);
              std::cout << "\n";
              l3("Joe");
              l3("Joo");
              l3(1.5);
          }
          

          基本上相当于:

          #include <iostream>
          struct F1
          {
              template < typename T >
              void operator ()(T a)
              {
                  static int l = 0; std::cout << a << " " << ++l << std::endl;;
              }
          };
          auto l1 = F1{};
          
          static int m = 0;
          struct F2
          {
          
              template < typename T >
              void operator ()(T a)
              {
                  std::cout << a << " " << ++m << std::endl;
              }
          };
          auto l2 = F2{};
          
          struct F3
          {
              F3(int n)
               :n(n)
              {
              }
          
              template < typename T >
              void operator ()(T a)
              {
                  std::cout << a << " " << ++n << std::endl;
              }
              int n;
          };
          int n = 0;
          auto l3 = F3{n};
          
          int main(){
              l1("Joe");
              l1("Joo");
              l1(1.5);
              std::cout << "\n";
              l2("Joe");
              l2("Joo");
              l2(1.5);
              std::cout << "\n";
              l3("Joe");
              l3("Joo");
              l3(1.5);
          }
          

          F1 中,每个具有不同类型的operator () 实例都会获得l 变量的不同实例,因此您会得到121 的结果。

          F2 中,operator () 的所有实例都共享同一个静态全局变量,因此您会得到123 的结果。

          F3 中,operator () 的所有实例都共享同一个成员(在 lambda 的情况下捕获)变量,因此您会得到结果 123

          【讨论】:

            【解决方案5】:

            代码 1 与代码 2

            代码 1 和 2 之间的区别只是在 lambda 内部与外部实例化变量 l。无法保证代码 2 会看到 l,而且您绝对不应该更新 l,因为它没有被 lambda 捕获。

            代码 2 与代码 3

            这里的区别在于您已经捕获了l[l],这意味着 lambda 可以在其范围内访问该变量。您还将 lambda 定义为 mutable,这意味着您可以更新已捕获的变量(在本例中为 l)。

            代码 3 是正确的版本。由于您正在尝试访问和更新变量 l ,因此您需要捕获它 [l] 并将 lambda 定义为可变的,以便您可以更新它。

            【讨论】:

              【解决方案6】:

              代码 1

              auto l1 = [](auto a) { static int l = 0; std::cout << a << " " << ++l << std::endl; };
                  l1("Joe");
                  l1("Joo");
                  l1(1.5);
              

              lambda 的实现

              class _tmp_lambda
              {
               public:
               _tmp_lambda() {}
               void operator()(const char * a) const
               {
                       static int l = 0;
                       std::cout << a << " " << ++l << std::endl;
               }
               void operator()(double a) const
               {
                       static int l = 0;
                       std::cout << a << " " << ++l << std::endl;
               }
              };
              

              代码 2

              static int l = 0;
              auto l1 = [](auto a) {  std::cout << a << " " << ++l << std::endl; };
              

              lambda 的实现

              class _tmp_lambda
              private:
              static int l = 0; /// <--
              public: 
              {
               _tmp_lambda() {}
               void operator()(const char * a) const
               {
                       std::cout << a << " " << ++l << std::endl;
               }
               void operator()(double a) const
               {
                       std::cout << a << " " << ++l << std::endl;
               }
              };
              

              代码 3

              int l = 0;
              auto l1 = [l](auto a) mutable {  std::cout << a << " " << ++l << std::endl; };
              

              lambda 的实现

              class _tmp_lambda
              private:
              int l = 0; /// <--
              public: 
              {
               _tmp_lambda(int _l):l(_l) {}
               void operator()(const char * a) // Not a const 
               {
                       std::cout << a << " " << ++l << std::endl;
               }
               void operator()(double a) // Not a const
               {
                       std::cout << a << " " << ++l << std::endl;
               }
              };
              

              上面的实现可以解释你所有的输出。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2016-09-28
                • 2018-12-19
                • 1970-01-01
                • 1970-01-01
                • 2019-08-17
                • 2016-09-14
                • 2016-04-05
                相关资源
                最近更新 更多