2

I've been quite confused about what a closure is in C++. I've read this What is a 'Closure'? but nearly all answers are referring to JavaScript but I think there are some differences about closure between C++ and JavaScript. So I found it difficult to match the JavaScript description of closure to C++.

For example, nearly all answers are taking a function returning a function as an example to demostrate closure in JavaScript. But I don't find the similar pattern in C++.

What's more, in JavaScript there is no such thing called "capture list".


  1. I was told that if a function uses non-local variables (from outer scope or global scope), then it's a closure. Is it correct?

Example 1:

    int a = 3;

    int am_I_a_closure(int c){
        return c + a;
    }

    int main(){
    }
  1. Why capture list is required? Can't lambda in C++ just work like JavaScript nested functions? Or in another way of speaking, can't lambda in C++ just work like the same way as global function accessing global(non-local) variables?

I mean, through normal name look-up proccess, if a name is not found in the current scope, then find it in the outer scoper, then more outer scoper...

Why capture list is needed? Why need to capture outer scope variables? Can't that be done through normal name look-up?

Example 2:

int main(){
    int a = 3;
    {
        int b = 5;
        {
            int c = 4;
            {
                std::cout << a+b+c <<std::endl;
            }
        }
    }
}

Example 3:

int main(){
    std::vector<int> values = {1,5,3,4,3};
    int a = 3;
    std::find_if(values.begin(), values.end(), [](int value) {return value > a; }); //Error, `a` is not captured.
}

Again, in Example 3, why a is need to be captured instead of normal name look-up as in Example 1 and Example 2?

Rick
  • 7,007
  • 2
  • 49
  • 79

1 Answers1

5

It's important to understand that "closure" is a concept that has a very specific meaning in functional programming. C++ however is not a functional language; it doesn't care all that much about strict adherence to functional programming terminology. It simply defines various functionality, some of which may or may not map well onto that terminology.

JavaScript and C++ are different languages. In JavaScript, a function has a property called being a "first-class object". This means that, when you execute the code to create a "function", you are creating an object that represents that function.A variable containing a function is fundamentally no different from a variable containing a string or a variable containing an array or whatever else. You can overwrite a variable that contains a function with an array, or vice-versa.

In particular, functions as first-class objects can have state associated with them at the point of their creation. If such a function reaches out of its scope to access a local variable, then that scope can be stored as part of the function's state; this state will be accessed automatically when you attempt to use that variable in the function. So it appears that you're reaching "out" of the function's scope, but you're not; the scope was brought "in" with you, and you're just accessing that.

In C++, a function is not a first-class object. You can get a pointer to a function, but function pointers are explicitly distinct from object pointers (casting between the two is not even required to be valid). A function is not "created" or "destroyed" as far as the C++ language is concerned; every function is always there, from the start of the program to its end.

C++ functions can access global variables, but that's because they're global. The location of a global variable is baked into the executable at compile/link time, so no special state needs to be stored with the function in order to access it.

However, C++ does have a useful concept that can help to create the effect of a first-class function object. Namely, a class type can overload the function call operator operator(). This allows an instance of a class to be called as if it were a function. Class instances are objects and can have internal state (aka: member variables), and the operator() overload is just a member function of the type.

Given all of that, you can create something that simulates a properly scoped function object. All you need is a class that has member variables that correspond to the variables outside of the function's scope which it references. These members can be initialized in the class's constructor by passing the external values to the constructor. Then you have a valid object which you can call, and it can access those "external" variables by using its member variables.

This is all a C++ lambda is. It wraps all of this up in "nice, neat" syntax. It writes a class for you; it writes the member variables you "capture" from the external world for you, and it calls the constructor and passes those variables for you.

However, C++ is a language that tries hard not to make something more expensive than you need it to be. The more external variables you use, the more internal member variables the lambda will need, and thus the bigger the class will be and the longer it will take to initialize/copy/etc. Therefore, if you want to use some external variable (which is implemented as a member variable), C++ requires you to either explicitly list it (so that you know that you meant to capture it) or to use the default capture mechanisms [=] or [&] (so that you are explicitly giving up your right to complain about accidentally making your lambda type huge and/or slow).

Furthermore, in JavaScript, everything is a reference. Variables store references to arrays, functions, dictionaries, etc. JavaScript is a reference-based language.

C++ is a value-oriented language. A variable in JavaScript references an object; a variable in C++ is an object. You cannot replace one object with another in C++; you may copy over the value of an object, but it is still that object.

As such, how a lambda ought to capture a particular variable becomes relevant. You can capture variables by value (copying the value into the hidden member) or by reference (referencing the object).

This is of particular importance because C++ is not garbage collected. That means that, just because you have a reference to an object does not mean the object still exists. If you have a variable on the stack, and you get a reference to it, and that reference exists past the point where the stack variable goes out of scope... that reference is now useless. In JavaScript, it'd be fine because of garbage collecting. But C++ doesn't do that. You have a reference to a destroyed object, which cannot be used.

So if you want a lambda to capture local variables and you want the lambda to persist past the point where the variables no longer exist, you will need to capture such variables by value, not by reference.

Capturing by value or by reference is determined by how you list the variable in the list of captures. &x means to capture by reference, while x is a capture by value. The default capture [=] means to capture by value by default, with [&] meaning reference capture by default.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • @Scheff: My point in stating that about "closures" is that the concept is not one that C++ defines. A "closure" is not a thing in C++, so asking about whether accessing a global variable from a function counts as one is just not a valid question as far as the language is concerned. – Nicol Bolas May 05 '20 at 05:46
  • I just followed the link the OP mentioned. Sorry, I withdraw my complaint. ;-) However, you nicely explained functors (classes with `operator()()`). Isn't that what's very close to a closure? Or is it like OOP in C - it's possible to do that but it's not directly supported by the language itself? – Scheff's Cat May 05 '20 at 05:49
  • Thank you for your great answer. It clears my mind. I did feel kinda weird when I tried to match those patterns from JavaScript to C++. I still have several questions and wish you could respond. Q1: As you mentioned, "*If such a function reaches out of its scope to access a local variable, then that scope can be stored as part of the function's **state**; this **state** will be accessed automatically when you attempt to use that variable in the function.*", in JS, inner scope reaches outer scoper variables, through the concept/property called **state**. OK. – Rick May 05 '20 at 13:56
  • Then what is the mechanism behind the scene in C++ when inner scope can access outer scope variables, if that's not the same way as how JavaScript works? I am not referring to accessing global variables, as you said, they are baked into the executable. I am talking about the situation shown in **Example 2**, as you can see, the innermost scope can access `a`, `b` and `c`. Do we have the concept of "state" in C++? – Rick May 05 '20 at 14:01
  • Or I think maybe it's just JavaScript's outer scope can **persist** while other languages for exmaple C++ can't? (I think this assumption might be better :P ) – Rick May 05 '20 at 14:07
  • Q2(not a question actually): Again, I do agree with you about that c++ lamdas are not real "closures". I had this feeling the first time I compared c++ lamdas to JS closures. So I think this question might really need some modification. [Do we have closures in C++?](https://stackoverflow.com/questions/12635184/do-we-have-closures-in-c). Now I notice that there are some objections here, which I overlooked before. – Rick May 05 '20 at 14:13
  • Q3: I see a post (http://scottmeyers.blogspot.com/2013/05/lambdas-vs-closures.html) of Scott Meyers. He's calling it "closure"? How? Why? Maybe he's using a broder definition of "closure"? I guess so. Looking forward to your reply :). – Rick May 05 '20 at 14:18
  • Hmm I do believe sometimes people are referring "closure" as a broader concept. https://www.wikiwand.com/en/Closure_(computer_programming)#/Closure-like_constructs , the subtitle is "Closure-like_constructs", which mentions C++ lambda laterly. – Rick May 05 '20 at 14:36
  • OK, Stack Overflow isn't a forum; we're not doing the thing where you repeatedly ask followup question after followup question that are increasingly off-topic. I answered your question as it existed; if you have another question, you can use the "Ask Question" button for that. But really, all this stuff with "is this really a closure" or not is utterly irrelevant and academic. There are language features that do useful things, so go use them. – Nicol Bolas May 05 '20 at 14:55