【问题标题】:How to create unique type for lambdas?如何为 lambdas 创建唯一类型?
【发布时间】:2013-10-18 11:54:09
【问题描述】:

我想为仅从第二次迭代运行的 for 循环创建一个辅助函数,因为我有很多具有以下模式的代码:

firstItem = true;
for (unsigned i = 0; i < 5; ++i)
{
  firstItem ? firstItem = false : std::cout << ",\n";
  std::cout << i;
}

我开始为此考虑一个辅助函数,并想出了这个解决方案:

template <typename T>
void fromSecondIter(T fn)
{
  static bool firstItem = true; // because of this it works with unique types only
  if (firstItem)
    firstItem = false;
  else
    fn();
}

我的问题是,当 T 是唯一类型时,此解决方案可以正常工作。所以传递一个 lambda 是可以的,但是传递一个函数会默默地导致错误。然后我将该 fn 参数包装到 lambda 中,但令人惊讶的是它并没有帮助;

这是一个完整的例子:

#include <type_traits>
#include <iostream>
#include <functional>

template <typename T>
void fromSecondIter(T fn)
{
  static bool firstItem = true;
  if (firstItem)
    firstItem = false;
  else
    fn();
}

template <typename T>
void iterTest(T fn)
{
  std::cout << "Numbers: ";
  for (unsigned i = 0; i < 5; ++i)
  {
    fromSecondIter([&fn](){ fn(); }); // bad, why lambda is not unique here???
    std::cout << i;
  }
  std::cout << std::endl;
}

void foo()
{
  std::cout << ", ";
}

void test()
{
  iterTest([](){ std::cout << ", "; }); // ok, lambda is unique
  iterTest([](){ std::cout << ", "; }); // ok, lambda is unique
  iterTest(foo); // ok
  iterTest(foo); // bad
}

int main()
{
  test();

  return 0;
}

打印出来:

Numbers: 0, 1, 2, 3, 4
Numbers: 0, 1, 2, 3, 4
Numbers: 0, 1, 2, 3, 4
Numbers: , 0, 1, 2, 3, 4

【问题讨论】:

  • 每个 lambda 字面量都有唯一的类型,而不是 lambda 对象的每个实例。
  • «此解决方案工作正常» ― 对于“正确”的一些定义。你试过嵌套循环吗?
  • @jrok:我认为这是真正的答案
  • @n.m.:你说得对,它不是可重入的

标签: c++ templates c++11 lambda


【解决方案1】:

Lambda 表达式创建唯一类型,但这并不意味着每次执行在代码中通过一个新的唯一类型时都会创建:

int main() {
  for (int i=0; i<3; ++i) {
    [] { std::cout << "Hello, World\n"; }();
  }
}

上面的 for 循环创建了三个相同类型的对象。

在您的代码中,您有一个模板函数iterTest,其中包含一个 lambda 表达式。这意味着对于iterTest 的每个实例化,lambda 表达式都将具有唯一的类型。但是,如果您运行 iterTest 的相同实例化,则 lambda 表达式每次都具有相同的类型。这就是这里发生的事情。

作为一个例子,这里有一个解决方法,对您的示例代码进行了最小的更改:

template <typename T>
void iterTest(T fn)
{
  std::cout << "Numbers: ";
  for (unsigned i = 0; i < 5; ++i)
  {
    fromSecondIter(fn);
    std::cout << i;
  }
  std::cout << std::endl;
}

void test()
{
  iterTest([](){ std::cout << ", "; }); // ok, lambda is unique
  iterTest([](){ std::cout << ", "; }); // ok, lambda is unique
  iterTest([]{foo();}); // ok
  iterTest([]{foo();}); // also ok
}

您当前的fromSecondIter 实现将创建一个静态变量,该变量从创建到程序结束都持续存在,并且将从程序开始时为它保留内存。这不是一个巨大的成本,但有更好的方法;创建一个具有 bool 成员变量的对象,以及具有必要逻辑的成员函数(例如,operator())。

#include <iostream>

struct skipper {
  bool first = true;

  template<typename Fn>
  void operator() (Fn &&fn) {
    if (first)
      first = false;
    else
      fn();
  }
};

int main()
{
  skipper skip_first;
  for (int i=0; i<10; ++i) {
    skip_first([]{ std::cout << ", "; });
    std::cout << i;      
  }
}

这意味着使用它需要两行而不是一行,但现在您可以控制范围。

【讨论】:

    【解决方案2】:

    为每个 lambda 实例化模板函数会创建一个新的函数定义。因为 lambda 函数类型不同(即使它们具有相同的声明)。

    所以每个定义都有自己的static firstItem

    但是foo只有一个定义,那么static会在第一次调用foo后改变:

    iterTest([](){ std::cout << ", "; }); // iterTest, fromSecondIter for this lambda
    iterTest([](){ std::cout << ", "; }); // iterTest, fromSecondIter for this lambda
    iterTest(foo);                        // iterTest, fromSecondIter for this foo
    iterTest(foo);                        // uses above functions
    

    一个简单的例子来证明这个问题是:

    template <int i>
    struct foo
    {
        void operator()()
        {
            std::cout << ", ";
        }
    };
    
    
    void test()
    {
      iterTest([](){ std::cout << ", "; });
      iterTest([](){ std::cout << ", "; });
      iterTest(foo<1>());
      iterTest(foo<2>());
    }
    

    foo&lt;1&gt;()foo&lt;2&gt;() 是不同的类型,所以它会为它们生成两个不同的fromSecondIter。那么firstItem不变。

    【讨论】:

    • 你是对的,但我在 "fromSecondIter([&fn](){ fn(); });" 行中将 foo 包裹在 lambda 中
    • 您将foofn 传递给fromSecondIter[&amp;fn](){fn();}; 是否唯一并不重要。
    • 好吧,你是对的,但对我来说正确的答案来自@jrok:“每个 lambda 文字都有唯一的类型,而不是 lambda 对象的每个实例。”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-28
    • 2014-08-06
    • 1970-01-01
    • 2018-10-14
    • 1970-01-01
    • 2017-01-25
    相关资源
    最近更新 更多