c++functional-programmingclosures

How to reuse a "closure generator" in C++?


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 adders, 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 adders, 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 commons 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.


Solution

  • 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.