Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are C++ lambdas true closures? Capturing by reference

Tags:

c++

lambda

c++14

In the code below, I create a lambda that captures a local variable by reference. Note that it is a pointer, so, if C++ lambdas are true closures, it should survive the lifetime of the function that creates the lambda.

However, when I call it again, rather than creating a new local variable (a new environment) it reuses the same as before, and in fact, captures exactly the same pointer as before.

This seems wrong. Either, C++ lambdas are not true closures, or is my code incorrect?

Thank you for any help

#include <iostream>
#include <functional>
#include <memory>

std::function<int()> create_counter()
{
    std::shared_ptr<int> counter = std::make_shared<int>(0);

    auto f = [&] () -> int { return ++(*counter); };

    return f;
}


int main()
{
   auto counter1 = create_counter();
   auto counter2 = create_counter();

   std::cout << counter1() << std::endl;
   std::cout << counter1() << std::endl;

   std::cout << counter2() << std::endl;
   std::cout << counter2() << std::endl;

   std::cout << counter1() << std::endl;


   return 0;
}

This code returns:

1
2
3
4
5

But I was expecting it to return:

1
2
1
2
3

Further edit:

Thank you for pointing the error in my original code. I see now that what is happening is that the pointer gets deleted after the invocation of create_couter, and the new create simply reuses the same memory address.

Which brings me to my real question then, what I want to do is this:

std::function<int()> create_counter()
{
    int counter = 0;

    auto f = [&] () -> int { return ++counter; };

    return f;
}

If C++ lambdas were true closures, each local counter will coexist with the returned function (the function carries its environment--at least part of it). Instead, counter is destroyed after the invocation of create_counter, and calling the returned function creates a segmentation fault. That is not the expected behaviour of a closure.

Marco A has suggested a work around: make the pointer passed by copy. That increases the reference counter, so it does not get destroyed after create_counter. But that is kludge. But, as Marco pointed out, it works and does exactly what I was expecting.

Jarod42 proposes to declare the variable, and initialize it as part of the capture list. But that defeats the purpose of the closure, as the variables are then local to the function, not to the environment where the function is created.

apple apple proposes using a static counter. But that is a workaround to avoid the destruction of the variable at the end of create_function, and it means that all returned functions share the same variable, not the environment under which they run.

So i guess the conclusion (unless somebody can shed more light) is that lambdas in C++ are not true closures.

thank you again for your comments.

like image 481
dmg Avatar asked Nov 23 '16 09:11

dmg


People also ask

Are lambda functions closures?

Lambda functions may be implemented as closures, but they are not closures themselves. This really depends on the context in which you use your application and the environment. When you are creating a lambda function that uses non-local variables, it must be implemented as a closure.

Are lambdas always inlined?

All lambdas are inline. Not all calls to them are necessarily inlined.

Are lambda captures Const?

By default, variables are captured by const value . This means when the lambda is created, the lambda captures a constant copy of the outer scope variable, which means that the lambda is not allowed to modify them.

Are C++ lambdas closures?

In C++, lambda expression constructs a closure, an unnamed function object capable of capturing variables in scope. It still sounds ambiguous, at least to me. Closure is a general concept in programming that originated from functional programming.


3 Answers

The shared pointer is being destroyed at the end of the function scope and the memory is being freed: you're storing a dangling reference

std::function<int()> create_counter()
{
  std::shared_ptr<int> counter = std::make_shared<int>(0);

  auto f = [&]() -> int { return ++(*counter); };

  return f;
} // counter gets destroyed

Therefore invoking undefined behavior. Test it for yourself by substituting the integer with a class or struct and check if the destructor actually gets called.

Capturing by value would have incremented the usage counter of the shared pointer and prevented the problem

auto f = [=]() -> int { return ++(*counter); };
          ^
like image 113
Marco A. Avatar answered Oct 22 '22 09:10

Marco A.


As mentioned, you have dangling reference as the local variable is destroyed at end of the scope.

You can simplify your function to

std::function<int()> create_counter()
{
    int counter = 0;

    return [=] () mutable -> int { return ++counter; };
}

or even (in C++14)

auto create_counter()
{
    return [counter = 0] () mutable -> int { return ++counter; };
}

Demo

like image 20
Jarod42 Avatar answered Oct 22 '22 09:10

Jarod42


Lambda expression — A lambda expression specifies an object specified inline, not just a function without a name, capable of capturing variables in scope.

  1. Lambdas can frequently be passed around as objects.
  2. In addition to its own function parameters, a lambda expression can refer to local variables in the scope of its definition.

Closures -

Closures are special functions that can capture the environment, i.e. variables within a lexical scope*.*

A closure is any function that closes over the environment in which it was defined. This means that it can access variables, not in its parameter list.

  • What is C++ specific part here A closure is a general concept in programming that originated from functional programming. When we talk about the closures in C++, they always come with lambda expressions (some scholars prefer the inclusion of function object in this)

In c++ a lambda expression is the syntax used to create a special temporary object that behaves similarly to how function objects behave.

The C++ standard specifically refers to this type of object as a closure object. This is a little bit at odds with the broader definition of a closure, which refers to any function, anonymous or not, that captures variables from the environment they are defined in.

As far as the standard is concerned, all instantiations of lambda expressions are closure objects, even if they don’t have any captures in their capture group.

https://pranayaggarwal25.medium.com/lambdas-closures-c-d5f16211de9a

like image 38
Pranay Kumar Avatar answered Oct 22 '22 11:10

Pranay Kumar