Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic lambda with std::function does not capture variables

Tags:

c++

gcc

c++14

I'm trying to use the generic lambda of C++14, but got a trouble with std::function.

#include <iostream>
#include <functional>

int main()
{
    const int a = 2;
    std::function<void(int)> f = [&](auto b) { std::cout << a << ", " << b << std::endl; };
    f(3);
}

This fails to compile with an error message saying that error: ‘a’ was not declared in this scope.

It works if I change it to (int b).

Is it a bug? or am I missing something?

The version of GCC i'm using is 4.9.2.

like image 886
Inbae Jeong Avatar asked Jan 25 '15 20:01

Inbae Jeong


People also ask

How do you capture variables in lambda?

Capture clause A lambda can introduce new variables in its body (in C++14), and it can also access, or capture, variables from the surrounding scope. A lambda begins with the capture clause. It specifies which variables are captured, and whether the capture is by value or by reference.

Can std :: function store lambda?

Instances of std::function can store, copy, and invoke any CopyConstructible Callable target -- functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members.

Can lambda function access global variables C++?

Yes, sure. Normal name lookup rules apply.

How do you pass a lambda function in C++?

Permalink. All the alternatives to passing a lambda by value actually capture a lambda's address, be it by const l-value reference, by non-const l-value reference, by universal reference, or by pointer.


2 Answers

I can reproduce this unless I do any of the following:

  • remove const from a
  • name a in the capture-list
  • change std::function<void(int)> to auto
  • make the lambda non-generic by changing auto b to int b
  • use Clang (e.g. v3.5.0)

I believe that this is a compiler bug related to optimisations and a failure to detect odr-use in a generic lambda (though it's interesting that setting -O0 has no effect). It could be related to bug 61814 but I don't think it's quite the same thing, therefore:

I have raised it as GCC bug 64791.

  • (Update: this bug has since been marked as fixed in GCC 5.0.)

Certainly I can't find anything obvious in the C++14 wording that should disallow your code, though there's very little "obvious" in general in the new C++14 wording. :(


[C++14: 5.1.2/6]: [..] For a generic lambda with no lambda-capture, the closure type has a public non-virtual non-explicit const conversion function template to pointer to function. The conversion function template has the same invented template-parameter-list, and the pointer to function has the same parameter types, as the function call operator template. [..]

[C++14: 5.1.2/12]: A lambda-expression with an associated capture-default that does not explicitly capture this or a variable with automatic storage duration (this excludes any id-expression that has been found to refer to an init-capture's associated non-static data member), is said to implicitly capture the entity (i.e., this or a variable) if the compound-statement:

  • odr-uses (3.2) the entity, or
  • names the entity in a potentially-evaluated expression (3.2) where the enclosing full-expression depends on a generic lambda parameter declared within the reaching scope of the lambda-expression.

[ Example:

void f(int, const int (&)[2] = {}) { } // #1
void f(const int&, const int (&)[1]) { } // #2
void test() {
  const int x = 17;
  auto g = [](auto a) {
    f(x); // OK: calls #1, does not capture x
  };

  auto g2 = [=](auto a) {
    int selector[sizeof(a) == 1 ? 1 : 2]{};
    f(x, selector); // OK: is a dependent expression, so captures x
  };
}

—end example ] All such implicitly captured entities shall be declared within the reaching scope of the lambda expression. [ Note: The implicit capture of an entity by a nested lambda-expression can cause its implicit capture by the containing lambda-expression (see below). Implicit odr-uses of this can result in implicit capture. —end note ]

[C++14: 5.1.2/13]: An entity is captured if it is captured explicitly or implicitly. An entity captured by a lambda-expression is odr-used (3.2) in the scope containing the lambda-expression. [..]

like image 51
Lightness Races in Orbit Avatar answered Oct 10 '22 03:10

Lightness Races in Orbit


int main() {
    const int a = 2;
    auto f = [&](auto b) { std::cout << a << ", " << b << std::endl; };
    f(3);
}

Don't know if it should work with std::function but this works for sure.

Further investigation:

I created a class to mimic as closely as possible the lambda:

class Functor {
private:
  int const x;

public:
  Functor() : x{24} {}
  auto operator()(int b) const -> void { cout << x << " " << b << endl; }
};


std::function<auto(int)->void> f2 = Functor{};
f2(3); // <- this works

This suggests that your example should have worked. After all lambdas are the same in behavior with an object who has the operator() overloaded and fields for the captured variables.

If we change the class to get to the auto part:

This doesn't work:

class Functor {
private:
  int const x;

public:
  Functor() : x{24} {}
  auto operator()(auto b) const -> void { cout << x << " " << b << endl; }
};

std::function<auto(int)->void> f2 = Functor{}; // <-- doesn't work

However this works:

class Functor {
private:
  int const x;

public:
  Functor() : x{24} {}
  template <class T>
  auto operator()(T b) const -> void { cout << x << " " << b << endl; }
};

std::function<auto(int)->void> f2 = Functor{}; // <-- this works

So most likely it is related to the use of auto as parameter of lambda/functions, a feature new to C++14 so most likely without a mature implementation.

like image 44
bolov Avatar answered Oct 10 '22 02:10

bolov