I've known one problem that closures do not behave like other programming languages (who expands the lifetime of captured variables and have GC) in C++ if we write a code like this
auto generator() {
int common = 0;
auto adder1 = [=]() mutable {
++common;
return common;
};
auto adder2 = [=]() mutable {
++common;
return common;
};
return make_pair(adder1, adder2);
}
int main() {
auto pair = generator();
auto a1 = pair.first;
auto a2 = pair.second;
cout << a1() << " " << a2() << "\n";
return 0;
}
we are expected to get 1 2
, for that the common
is the "lexical environment" for both adder1
and adder2
. However, we'll get 1 1
, because common
is copied by value, not by reference.
A solution to this problem is to let common
be a static
one. But in my perspective, the static
does extend the lifetime of common
in a way we don't want. (Maybe I have some misunderstanding). So I would rather use another solution using another closure and initialized lambda captures (in C++14) to generate the adder
s, capturing common
by reference.
auto generator = [common = 0]() mutable {
auto adder1 = [&]() {
++common;
return common;
};
auto adder2 = [&]() {
++common;
return common;
};
return make_pair(adder1, adder2);
};
this time it works well and output 1 2
.
We use a function to initialize the adder
s, like the generator
above. In general, they are not "closures" or at least not the closure specifically wanted. That's why I regard them as "closure generators".
Even it seemed to work well in the second edition, the generator does not create different common
s for different (adder1, adder2)
pairs. If we call generator like this:
int main() {
auto pair1 = generator(); auto pair2 = generator();
auto a1 = pair1.first; auto a2 = pair1.second;
auto a3 = pair2.first; auto a4 = pair2.second;
a1(); a1(); a1(); a2(); a2(); a2(); a2(); a3(); a4();
cout << a1() << " " << a2() << " " << a3() << " " << a4() << "\n";
return 0;
}
it will output 10 11 12 13
but not 8 9 3 4
. Because the generator itself is a closure, hence the common
assigned to pair1
and pair2
is the same one.
So I'm wondering whether there exists a way in C++ enabling us to reuse the "generator", no matter whether it is a closure or not. An implementation without pointer is prefered. Thanks.
generator
stores state common
and should be an object, so that C++ can manage the lifetime of that state. Consider just:
#include <functional>
#include <iostream>
class generator {
int common = 0;
int get() { return ++common; }
public:
std::function<int()> first = std::bind(&generator::get, this);
std::function<int()> second = std::bind(&generator::get, this);
};
int main() {
auto pair = generator(); // lifetime of common begins
auto a1 = pair.first;
auto a2 = pair.second;
std::cout << a1() << " " << a2() << "\n";
return 0; // lifetime of common ends
}
You can also manage lifetime differently. As you noted, you can make the lifetime static
. You could also use dynamic allocation or use std::shared_ptr
to extend the lifetime. I think the lifetime of common
should be bind to the lifetime of the pair - thus, it is an object that contains both the pair of functions and common
.