First of all, write down the whole process of how I encountered these problems
when I learned how to overload the function call operator, and I learned about function object; which led to the concept of function closures, lamda expression, lexical scope and finally First-Class Function.
here is the c++ code example
#include
using std::cout;
using std::endl;
struct Foo {
public:
int operator()(int a, int b) const{
return a + b;
}
};
void test()
{
Foo f1;
int a = 1, b = 2;
int c = f1(a, b);
cout << "c = " << c << endl;
}
int main()
{
test();
return 0;
}
Programming running results
c = 3
At first I thought f1
was both a Function Object
and a Closure
, but it is not; f1
is a function object , but it is not exactly a closure.
here is the definition of the Closure
: “In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions”.
when I finished reading this passage, what I thought at that moment was: what are lexical scope
, name binding
, and first-class functions
The following is a description of these two terms:
reference:https://stackoverflow.com/questions/1047454/what-is-lexical-scope
First, lexical scope (also called static scope), in C-like syntax:
void func()
{
int x = 5;
return;
void func2()
{
printf("%d\n", x);
}
}
Every inner level can access its outer levels.
So here we can try to give some possible explanations:
x
is automatically available to func2()
Here is the explanation of name binding:
In program language, name binding is the association of entities(data and/or code) with identifiers. An identifier bound to an object is said to reference that object. Binding is intimately connected with scoping, as scope determines which names bind to which objects - at which locations in the program code (lexically). and in which one of the possible execution paths(temporally).
and the lexically scoped name binding means static binding in c++, here are some details about Static Binding and Dynamic Binding
then what is a language with first-class functions
the definition of First-class Functions is: "In computer science, a programming language is said to have first-class functions if it treats functions as first-class citizens. This means the language supports passing functions as arguments to other functions, returning them as the values from other functions, as assigning them to variables or storing them in data structures.
But I prefer this explanation: First-class Functions are functions that can be treated like any other value. You can pass them to functions as arguments, return them from functions, and save them in variables.
//code examples in c++
//"[](int a, int b){ return a*b; }" actually called a lamda expression
// it will change to a copy of the function closure which insatantiation by the lamda
// we can see it as an anonymous function here
// we will talk about it in more detail later
//an argument of a function,
std::accumulate(vec.begin(), vec.end(), 1, [](int a, int b){ return a*b; });
//returned from a function
std::function madkAdd() {
return [](int a, int b){ return a + b; };
}
std::function add = makeAdd();
add(2021, 1);//2022
//assign it to variable
auto f = [](int x){ std::cout << "x = " << endl;};
f(10);//x = 10
here are some more details:
https://www.modernescpp.com/index.php/first-class-functions
https://lispcast.com/what-are-first-class-functions/
Go back to the previous question: why function object is not exactly a closure in c++?
As C++ does not allow defining functions and objects inside a function, function object does not (always) allow lexical scoping, where with lexical scope, a name always refers to its (more or less) local lexical environment.
So what is closure in C++?
The reference link is here
ok now we know what is the definition of closure
, and we can see in the First-class functions
part, we see these two statements:
auto f = [](int x){std::cout << "x = " << x << endl;};
f(10);//x = 10
In here, the expression [](int x){std::cout << "x = " << x << endl;}
is a lamda expression
, and it is just that: an expression
. As such, it exists only in a program’s source code. it does not exists at runtime, and the runtime object created by that expression is the closure
. f
is just a copy of the closure. The actual closure object is a temporary that’s typically destroyed at the end of the statement.
Scott Meyers got a good explanation to this using analogues. The distinction between a lamda
and the corresponding closure
is precisely equivalent to the distinction between a class
and an instance
of the class. A class exists only exists in source code; it doesn’t exists at runtime. What exists at runtime are objects of the class type. Closures
are to lamdas
as objects
are to classes
, because each lamda expression causes a unique class to be generated(during compilation) and also causes an object of that class type–a closure–to be created (at runtime).
The exception to this rule is when you bind the closure to a reference. The simplest way to do that is to employ a universal reference
//right value reference
auto&& rrefToClosure = [&](int x, int y) { return 2 * (x + y); };
std::cout << "rrefToClosure(1, 2) = " << rrefToClosure(1, 2) << std::endl; //rrefToClosre(1, 2) = 6
but binding it to an lvalue-reference-to-const
will also work:
//left value reference to const
const auto& lrefToConstToClosure = [&](int x, int y) { return 3 * (x + y); };
std::cout << "lrefToClosure(1, 2) = " << lrefToClosure(1, 2) << std::endl; //lrefToClosre(1, 2) = 9
the difference between right reference and left reference is as follows:
reference link are here
void printReference (const string& str)
{
cout << str;
}
void printReference (string&& str)
{
cout << str;
}
The first option can take lvalues because it’s an lvalue reference. It can take rvalues because it is marked const
and rvalues are allowed to bind to const
lvalue references.
The second version is only allowed non-const
rvalues because you can’t implicitly strip const
from the referencee and rvalue references don’t allow lvalues to bind to them.
The semantic difference is that the former(lref) function is saying “I am just going to read what you pass in here and I’d rather not copy it”, whereas the latter(rref) is saying “I reserve the right to rip the guts out of this object and paint my living room with them”.