在 C++11 中不允许重新定义 lambda,为啥?

Posted

技术标签:

【中文标题】在 C++11 中不允许重新定义 lambda,为啥?【英文标题】:Redefining lambdas not allowed in C++11, why?在 C++11 中不允许重新定义 lambda,为什么? 【发布时间】:2013-09-16 08:08:42 【问题描述】:

例子:

#include <functional>

int main() 
  auto test = [];
  test = [];
    
  return 0;

这会在 gcc 4.7.2 中发出以下错误消息:

test.cpp: In function ‘int main()’:
test.cpp:5:13: error: no match for ‘operator=’ in ‘test = <lambda closure object>main()::<lambda()>’
test.cpp:5:13: note: candidate is:
test.cpp:4:16: note: main()::<lambda()>& main()::<lambda()>::operator=(const main()::<lambda()>&) <deleted>
test.cpp:4:16: note:   no known conversion for argument 1 from ‘main()::<lambda()>’ to ‘const main()::<lambda()>&’

来自标准 5.1.2.3(强调我的):

一个实现可以定义不同于下面描述的闭包类型,前提是这不会改变程序的可观察行为,除非通过改变:

——闭包类型的大小和/或对齐方式,

——闭包类型是否可简单复制(第 9 条)

——闭包类型是否是标准布局类(第 9 条),或者

——闭包类型是否为 POD 类(第 9 条)。

据我所知,这就是我遇到的问题。它试图使用已删除的赋值运算符并失败。我很想知道是否有一个简单的解决方法,以及更广泛地说,允许 lambdas 通常省略复制可构造性的动机是什么。

【问题讨论】:

它没有尝试使用复制构造函数。它正在尝试使用赋值运算符。 不,阻止您使用赋值运算符的是 5.1.2.20 与 lambda 表达式关联的闭包类型具有已删除的 (8.4.3) 默认构造函数和已删除的复制赋值运算符.它有一个隐式声明的复制构造函数(12.8),并且可能有一个隐式声明的移动构造函数(12.8)。(嗯,那个,事实上 lambda 的类型不同) 如果你在第一个 lambda 之前加上 +,它就会神奇地开始工作。 @OmnipotentEntity 这纯粹是魔法,我不知道它是如何工作的! + 运算符是否启用了某些特殊功能!? @JohannesSchaub-litb 我想我解决了这个难题。评论有点长,所以我做了一个自我回答here。 【参考方案1】:

您似乎认为这两个 lambda 具有相同的类型,但事实并非如此。每个都创建自己的类型:

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

int main() 
  auto test = [];
  auto test2 = [];
  std::cout << std::is_same< decltype( test ), decltype( test2 ) >::value << std::endl;
  return 0;

将打印0。当然,你从编译器得到的错误信息在这方面可能会更清楚一点......

【讨论】:

参考:[expr.prim.lambda]/3 "lambda-expression [...] 的类型是唯一的、未命名的非联合类类型 —称为闭包类型" @DyP 正要在标准中搜索它,但你更快。谢谢:) 我不知道,unique这个词的用法几乎是喊出来的。 ;) 谢谢! 假设每个 lambda 表达式都是一个宏,它用重载的operator() 和包含捕获变量的成员变量将表达式替换为未命名的类定义。即使两个类的结构相同,它们仍然是不同的类型。 即使它们是相同的类型,您仍然无法复制它们,因为复制构造函数已被删除(请参阅 C++11 标准的第 5.1.2.20 节)。编译器甚至没有达到需要在失败之前检查类型是否匹配的地步。这就是您收到“no match for operator=" 错误的原因。请参阅我在回答中给出的示例。【参考方案2】:

类型 lambda 表达式 (这也是闭包对象的类型)是一个 unique,未命名的非 联合类类型

所以就像你在做以下事情:

struct  a;
struct  b;
a = b; // error, type mismatch

如果您想将具有相同签名的不同 lambda 分配给同一个变量,请使用 std::function

std::function<void()> f = [];
f = []; //ok

【讨论】:

或者,因为它们没有捕获,void(*f)() 也可以。这样会更有效率。 类型是唯一的,因为类型编码了要调用的特定函数;该函数只是类型的operator()。这允许在通用算法中内联函数,这对于函数指针或 std::function 是不可能的。 @JanHudec:有点,即使两个 lambda 表达式相同,它们仍然是不同的类型。 @user1131467:如果你有两个内容相同的类,它们仍然是不同的类型。【参考方案3】:

无法重新定义 Lambda,因为每个 lambda 都是不同的、匿名的、不兼容的类型。 只有将它们传递给能够推断出该类型的模板化函数(如std::function ctor)才能复制它们。

【讨论】:

【参考方案4】:

您无法执行此操作的原因是 lambda 表达式的复制赋值运算符已被声明为已删除,请参阅标准的第 5.1.2/20 节。如需更清晰(对于 clear 的不寻常定义),请参阅此代码示例

template<class T> void f(T x1)

  T x2 = x1; // copy constructor exists, this operation will succeed.
  x2 = x1; // assignment operator, deleted and will cause an error

int main()

  f([]);
  return 0;

其他答案指出每个 lambda 都有一个唯一的类型,但这不是您收到该错误的原因。这个例子表明,即使两个 lambda 具有相同的类型,它仍然无法复制它。但是,您可以将其复制到新变量中。这就是您的错误消息抱怨缺少 operator= 而不是它们的类型不同的原因。尽管每个 lambda 都有自己的类型也对您没有多大帮助。

【讨论】:

+1。您还应该在答案中添加来自标准的引用(顺便说一下,它是 5.1.2/19,而不是 5.1.2/20)。 @jogojapan 抱歉,这个答案是错误的。查看 OPs 错误消息:问题是不同的类型,尽管消息说 main()::&lt;lambda()&gt; 两次。较新版本的 GCC fixed the message。另请参阅我对 Rastaban 对我的回答的评论的评论。 @DanielFrey 哦,我明白了。感谢您指出了这一点。 (但不应该在完整的答案中仍然提到 5.1.2/19 吗?确实,lambda 表达式具有唯一的类型,但 5.1.2/3 中的措辞表明该类型对于 lambda 表达式是唯一的,而不是闭包对象。因此,如果相同的表达式[]在同一范围内使用两次,则类型仍然可以相同。此外,即使类型不同,也可以进行隐式转换..?) @jogojapan 5.1.2/3 指的是“lambda-expression”,这意味着在源中一次出现 lambda-expression。因此,我将其解读为这句话中的“唯一”意味着源中的每次出现都会创建一个唯一的类型,即使相同的标记序列出现两次,它仍然是两次出现,并且每个都有自己的类型。此外,这些类型没有隐式转换,也没有定义其他可以匹配的转换。 @jogojapan ...继续:但即使这个答案并不能真正解释 OPs 的直接问题,我认为将其保留在这里是值得的。它表明,除了我在回答中解决的潜在问题之外,他还通过假设即使 lambda 是可复制的,但在任何情况下它们仍然不可分配,从而提出了另一个想法。因此:拉斯塔班 +1 :)【参考方案5】:

如果我们可以将一个 lambda 分配给另一个不同类型的 lambda,我们如何将函数体/定义从该 lambda 复制到另一个?如果我们这么固执,那么我们可以使用一些成员std::function-like 类型作为将被复制的人。但这违反了不付钱的 C++ 规则......

【讨论】:

以上是关于在 C++11 中不允许重新定义 lambda,为啥?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C++11 中使用 Lambda 表达式

为啥 Python 的 `lambda` 表达式中不允许赋值?

访问 C++14 lambda 捕获,如结构成员

为啥在未计算的操作数中不允许使用 lambda 表达式,但在常量表达式的未计算部分中允许使用 lambda 表达式?

C++11 std::set lambda 比较函数

C++ lambda 友谊