-4
#include <iostream>
#include <functional>
#include <utility>

using namespace std;

typedef std::function<void(const string&, const string&, const bool, const bool)> 
    Callback_T;

class A {
public:
    void
    process(const string &a, const string &b, const bool c, const bool d, const int e)
    {
        cout << "a: " << a << " b: " << b << " c: " << c << " d: " << d << " e: " << e << endl;
    }

    Callback_T
    constructCallback(const int &e)
    {
        Callback_T callback =
        [&, this, e](auto&&...args) // <--- here, e must be captured by value, why?
        {
            this->process(
            std::forward<decltype(args)>(args)...,
            e);
        };

        return callback;
    }
};

int main()
{
    A a;
    auto cb = a.constructCallback(20);
    cb("A", "B", true, false);
}

The above program outputs: "a: A b: B c: 1 d: 0 e: 20" But if I change that line which capture e to:

[&, this, &e]

It outputs: "a: A b: B c: 1 d: 0 e: 26340408", seems indicating e is not defined/initialized.

Why only capture it by value works?

2 Answers2

3

What you have is a dangling reference. Since e is a reference parameter it is bound to something else. In this case it is a temporary object created from the literal 20. This temporary goes out of scope when the function ends leaving callback with a reference to an object that no longer exists.

When you capture by value you negate this issue as the lambda will store it's own copy of e ensuring that it is still valid after constructCallback returns.


When capturing by reference you must ensure that no path will leave you with a reference to something that doesn't exist.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
0

Because e is out of scope when the function is invoked, and you have a dangling reference. In the following lines:

A a;
auto cb = a.constructCallback(20);
cb("A", "B", true, false);

when constructCallback(20) is called, a local e is created with value 20, which is captured by reference to your lambda, then destroyed when the function exits. Using this reference is thus undefined behavior and leads to the garbage value that you observe. If you capture e by value instead, it is copied, and lives for as long as the lambda. If you really need to use e by reference, you need to ensure that it is not destroyed before the lambda is evaluated.

alter_igel
  • 6,899
  • 3
  • 21
  • 40
  • so even if I change the `e` to be passed in by value: `constructCallback(const int e)`, it is still a temporary and will still go out of scope before the callback is evaluated, correct? (but doing so seems to be printing 20, why?) – Lei Mao Jul 09 '18 at 21:45
  • @LeiMao in that case, you're still binding a reference to the function's argument, which will be out of scope after the function returns. It's still undefined behavior, it just so happens that `20` hasn't yet been overwritten in the memory that used to belong to `e`. This is still very bad and needs to be avoided if you want your program to work predictably. – alter_igel Jul 09 '18 at 21:58
  • You could change your function to `constructCallback(int& e)` and pass a variable instead of a temporary literal like `20`. In this case, you need to make sure that variable still exists when the callback is invoked, and for all the same reasons mentioned above. The only thing this would buy you is that changing the variable changes what value the callback sees. It's up to you if you want that. – alter_igel Jul 09 '18 at 22:02