简体   繁体   中英

Generic lambda with std::function does not capture variables

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.

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 (eg 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 (ie, 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 . [..]

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM