为啥 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?的主要内容,如果未能解决你的问题,请参考以下文章

隐式运算符和 lambda

为啥 lambda auto& 参数选择 const 重载?

为啥首先允许指针从非常量到常量的隐式转换?

C++Lambda表达式作为参数

C++Lambda表达式作为参数

C++Lambda表达式作为参数