24

I have a small "lambda expression" in the below function:

int main()
{
    int x = 10;
    auto lambda = [=] () { return x + 3; };
}

Below is the "anonymous closure class" generated for the above lambda expression.

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_19{x};
}

The closure's "operator()" generated by the compiler is implicitly const. Why did the standard committee make it const by default?

max66
  • 65,235
  • 10
  • 71
  • 111
cpp_enthusiast
  • 1,239
  • 10
  • 28
  • `__lambda_3_19` is UB. Would you mind changing the token name to keep the UB pedants away>? – Bathsheba Nov 23 '18 at 11:30
  • sorry what is UB? – cpp_enthusiast Nov 23 '18 at 11:30
  • 10
    @Bathsheba from what I understand it's compiler-generated, so no UB there – Ap31 Nov 23 '18 at 11:30
  • The closure class you show is a *possible* variant. The implementation (compiler) could generate something completely different. – Some programmer dude Nov 23 '18 at 11:30
  • @Ap31: Good point, back in my box. – Bathsheba Nov 23 '18 at 11:31
  • @Someprogrammerdude I agree that it is compiler dependent. But it doesn't matter what compiler you will use, the standard says that the operator() is const by default. – cpp_enthusiast Nov 23 '18 at 11:32
  • @Bathsheba: Please check the below link: https://cppinsights.io/lnk?code=ICNpbmNsdWRlIDx2ZWN0b3I+CgppbnQgbWFpbigpIAp7Cgl2b2xhdGlsZSBpbnQgbXlfbWVhc2x5X2ludCA9IDEwOwoJWz1dKGludCBpKXsgcmV0dXJuIG15X21lYXNseV9pbnQgKyBpOyB9Owp9&rev=1.0 – cpp_enthusiast Nov 23 '18 at 11:32
  • why would it not make it `const` though? why would it limit the use of your lambda on purpose? – Ap31 Nov 23 '18 at 11:33
  • 3
    There is an school of thought that all variables should be const by default. Perhaps this kind of thinking had some influence? – Galik Nov 23 '18 at 11:35
  • That part is true. And it's because lambdas by default are *immutable*. – Some programmer dude Nov 23 '18 at 11:35
  • @Someprogrammerdude: I understand that they are immutable by default. But why is the question? There should be some reason. – cpp_enthusiast Nov 23 '18 at 11:37
  • @Galik: Yes, if you use "rust" language, then all variables are const by default. But in cpp, they aren't. – cpp_enthusiast Nov 23 '18 at 11:38
  • Maybe to catch cases, when a variable passed as a non-reference by mistake, and it is modified (so the code modifies the copy, not the original variable, which was not the intent)? – geza Nov 23 '18 at 11:38
  • @gurram Well, the lambdas are... :P – Galik Nov 23 '18 at 11:39
  • 1
    @gurram I think the question should be why not? what could be the reason for making it non-const, thus limiting your lambda for no reason? – Ap31 Nov 23 '18 at 11:39
  • @Ap31: Let's think of the below scenario: when you perform a copy-by-value, the values inside the lambda are copy of the original values. I just want to modify the copy but not the original to perform some operations. In this case, making the operator() const doesn't help. – cpp_enthusiast Nov 23 '18 at 11:44
  • related/maybe duplicate: https://stackoverflow.com/questions/5501959/why-does-c11s-lambda-require-mutable-keyword-for-capture-by-value-by-defau – geza Nov 23 '18 at 11:46
  • 2
    @gurram Think about capturing a pointer by value, which copies the pointer and not what it points to. If you're able to call non-const functions of the object, then that could modify the object, possibly in ways that are unwanted or leads to UB. If the `operator()` function is marked as `const` then that's not possible. – Some programmer dude Nov 23 '18 at 11:47
  • I mean, given the design of the class, an object represents the evaluation of the formula for a given parameter (/several given parameters). An object of that class is therefore immutable, as changing it's members (values of the parameters) would remove its identity in the sense of an object in OOP thinking. Therefore, all methods on it are const by the semantics. – Aziuth Nov 23 '18 at 11:50
  • 2
    Its the wrong way around that we have to declare member functions explicitly as `const` and non-const is the default. Its weird that we are used to redundantly repeat the return type of a function when `auto` return types could be natural. In some sense lambdas give you a glimpse of how c++ could look like if it was reinvented from scratch today. – 463035818_is_not_an_ai Nov 23 '18 at 12:33
  • 1
    i guess the reason is simply that a missing `mutable` will result in a clear compiler error, while a missing `const` is an error that usually the compiler cannot diagnose – 463035818_is_not_an_ai Nov 23 '18 at 12:45

3 Answers3

18

Found this paper by Herb Sutter at open-std.org which discusses this matter.

The odd couple: Capture by value’s injected const and quirky mutable
Consider this strawman example, where the programmer captures a local variable by value and tries to modify the captured value (which is a member variable of the lambda object):

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’

This feature appears to have been added out of a concern that the user might not realize he got a copy, and in particular that since lambdas are copyable he might be changing a different lambda’s copy.

The above quote and example indicate why the Standards Committee might have made it const by default and required mutable to change it.

P.W
  • 26,289
  • 6
  • 39
  • 76
  • 1
    The "lambdas are copyable" argument seems a strong one. That would mean that passing the lambda to `std::sort`, then trying to use it again outside of `std::sort` (or in a second call to `std::sort`) wouldn't show any of the changes made by the calls in `std::sort`, right? – ShadowRanger Nov 23 '18 at 12:06
  • @ShadowRanger: That's how I understand it. I did not test it though. – P.W Nov 23 '18 at 12:08
10

From cppreference

Unless the keyword mutable was used in the lambda-expression, the function-call operator is const-qualified and the objects that were captured by copy are non-modifiable from inside this operator()

In your case, there is nothing that, captured by copy, is modifiable.

I suppose that, if you write something as

int x = 10;

auto lambda = [=] () mutable { x += 3; return x; };

the const should disappear

-- EDIT --

The OP precise

I already knew that adding mutable will solve the issue. The question is that I want to understand the reason behind making the lambda immutable by default.

I'm not a language lawyer but this seems me obvious: if you make operator() not const, you can't make something as

template <typename F>
void foo (F const & f)
 { f(); }

// ...

foo([]{ std::cout << "lambda!" << std::endl; });

I mean... if operator() isn't const, you can't use lambdas passing they as const reference.

And when isn't strictly needed, should be an unacceptable limitation.

max66
  • 65,235
  • 10
  • 71
  • 111
  • Passing a function-object by constant reference is strange. The standard-library (nearly?) always passes callables by value, expecting the caller to employ `std::reference_wrapper` as needed. – Deduplicator Nov 23 '18 at 12:01
  • @Deduplicator - I suppose depends on circumstances. Generally speaking, I don't see a good reason to impose that a callable is modifiable when the called method doens't change the object itself. – max66 Nov 23 '18 at 12:37
  • 1
    @Deduplicator Although passing by value or forwarding-reference is more common, passing const ref isn't really *strange*. I would think this answer is the reason for the `const` qualifier of `std::function`. But for a lambda, it's not convincing, since lambda can certainly be mutable by default, and require a `const` qualifier for const-ness. `[] () const { ... }` looks more consistent to me. – llllllllll Nov 23 '18 at 13:17
0

I think, it is simply to avoid confusion when a variable inside a lambda refer not to what was originally captured. Lexically such a variable is as if in scope of its "original". Copying is mainly to allow to extend the lifetime of object. When a capture is not by copy it refers to original and modifications are applied to the original, and there is no confusion because of two different objects (one of which is implicitly introduced), and it is allowed by lambda's const function call operator.

guest
  • 87
  • 3