为啥 lambda 的调用运算符隐式为 const?
Posted
技术标签:
【中文标题】为啥 lambda 的调用运算符隐式为 const?【英文标题】:Why is a lambda's call-operator implicitly const?为什么 lambda 的调用运算符隐式为 const? 【发布时间】:2019-04-26 00:24:39 【问题描述】:我在下面的函数中有一个小的“lambda 表达式”:
int main()
int x = 10;
auto lambda = [=] () return x + 3; ;
下面是为上述 lambda 表达式生成的“匿名闭包类”。
int main()
int x = 10;
class __lambda_3_19
public: inline /*constexpr */ int operator()() const
return x + 3;
private:
int x;
public: __lambda_3_19(int _x) : x_x
;
__lambda_3_19 lambda = __lambda_3_19x;
编译器生成的闭包“operator()”是隐式常量。为什么标委会默认设为const
?
【问题讨论】:
@Bathsheba 据我了解它是编译器生成的,所以那里没有 UB 有一种观点认为所有变量都应该默认为 const。或许这种想法产生了一些影响? @gurram 我认为问题应该是为什么不呢?将其设为非 const 的原因可能是什么,从而无缘无故地限制了您的 lambda? @gurram 考虑按值捕获指针,它复制指针而不是指针指向的内容。如果您能够调用对象的非常量函数,那么这可能会修改对象,可能是以不需要的方式或导致 UB。如果operator()
函数被标记为const
,那么这是不可能的。
我们必须将成员函数显式声明为const
,并且默认情况下是非const,这是错误的做法。当auto
返回类型可能很自然时,我们习惯于冗余重复函数的返回类型,这很奇怪。从某种意义上说,lambdas 让您可以一窥如果今天从头开始重新发明 c++ 会是什么样子。
【参考方案1】:
在 open-std.org 上发现了 Herb Sutter 的 paper,它讨论了这个问题。
奇怪的一对:按值的注入 const 和古怪的可变捕获 考虑这个稻草人的例子,程序员通过值捕获一个局部变量并尝试修改捕获的值(它是 lambda 对象的成员变量):
int val = 0; auto x = [=]( item e ) // look ma, [=] means explicit copy use( e, ++val ); ; // error: count is const, need ‘mutable’ auto y = [val]( item e ) // darnit, I really can’t get more explicit use( e, ++val ); ; // same error: count is const, need ‘mutable’
添加此功能似乎是因为担心用户可能没有意识到他得到了副本,特别是由于 lambda 是可复制的,因此他可能会更改不同的 lambda 副本。
以上引用和示例说明了为什么标准委员会可能默认将其设为const
并要求mutable
进行更改。
【讨论】:
“lambda 是可复制的”论点似乎是一个强有力的论点。这意味着将 lambda 传递给std::sort
,然后尝试在std::sort
之外再次使用它(或在第二次调用std::sort
时)不会显示std::sort
中的调用所做的任何更改对吧?
@ShadowRanger:我就是这么理解的。不过我没有测试它。【参考方案2】:
来自cppreference
除非在 lambda 表达式中使用关键字
mutable
,否则函数调用运算符是 const 限定的,并且通过复制捕获的对象在此operator()
中是不可修改的
在您的情况下,副本捕获的任何内容都无法修改。
我想,如果你把东西写成
int x = 10;
auto lambda = [=] () mutable x += 3; return x; ;
const
应该消失
-- 编辑--
OP 精确
我已经知道添加 mutable 可以解决问题。问题是我想了解使 lambda 默认不可变的原因。
我不是语言律师,但这似乎很明显:如果你让operator()
不是const
,你就不能做某事
template <typename F>
void foo (F const & f)
f();
// ...
foo([] std::cout << "lambda!" << std::endl; );
我的意思是...如果 operator()
不是 const
,则不能使用 lambdas 将它们作为 const
引用传递。
当不是严格需要时,应该是一个不可接受的限制。
【讨论】:
通过常量引用传递函数对象很奇怪。标准库(几乎?)总是按值传递可调用对象,期望调用者根据需要使用std::reference_wrapper
。
@Deduplicator - 我想取决于具体情况。一般来说,当被调用的方法不改变对象本身时,我认为没有充分的理由强加可修改的可调用对象。
@Deduplicator 虽然按值传递或转发引用更常见,但传递 const ref 并不真正奇怪。我认为这个答案是const
限定符std::function
的原因。但是对于 lambda,它并不令人信服,因为 lambda 在默认情况下当然可以是可变的,并且需要一个 const
限定符来表示 const-ness。 [] () const ...
在我看来更一致。【参考方案3】:
我认为,当 lambda 中的变量不引用最初捕获的内容时,这只是为了避免混淆。从词法上讲,这样的变量就好像在其“原始”范围内。复制主要是为了延长对象的生命周期。当捕获不是通过复制时,它指的是原始并且对原始应用了修改,并且不会因为两个不同的对象而混淆(其中一个是隐式引入的),并且它是 lambda 的 const 函数调用运算符所允许的。
【讨论】:
你应该可以。只需点击the edit link here。 “页面未找到”是结果。以上是关于为啥 lambda 的调用运算符隐式为 const?的主要内容,如果未能解决你的问题,请参考以下文章