Search code examples
c++lambda

C++: What if a lambda expression captures a r-value by reference


I am trying to work with the lambda expression to wrap some functions to void() ones. The code is as follows:

class VoidFunctionWrapper
{
public:
    VoidFunctionWrapper()
    {
        function_ = []() {};
    }

    template <typename Function, typename... Args>
    VoidFunctionWrapper(Function &&function, Args &&...args)
    {
        wrap_function(std::forward<Function>(function), std::forward<Args>(args)...);
    }

    void wrap_function(std::function<void()> function)
    {
        function_ = std::move(function);
    }

    template <typename Function, typename... Args>
    std::enable_if_t<!std::is_same_v<std::decay_t<Function>, std::function<void()>>, void>
    wrap_function(Function &&function, Args &&...args)
    {
        std::function<void()> function_lambda = [&]() mutable
        { function(std::forward<Args>(args)...); };

        wrap_function(function_lambda);
    }

    std::function<void()> get_wrapped_function()
    {
        return function_;
    }
private:
    std::function<void()> function_;
};

The class VoidFunctionWrapper wraps a function to a std::function<void()> instance. The key member function is:

    template <typename Function, typename... Args>
    std::enable_if_t<!std::is_same_v<std::decay_t<Function>, std::function<void()>>, void>
    wrap_function(Function &&function, Args &&...args)
    {
        std::function<void()> function_lambda = [&]() mutable
        { function(std::forward<Args>(args)...); };

        wrap_function(function_lambda);
    }

To state at the begining, I understand this function is not right because the capture list should use [=] instead of [&]. However, when using this "wrong" version of code with [&], I am really confused doing this test:

int main()
{
    // A test function to set a bool variable to true with a bool pointer
    // It changes the value and print the address

    std::function<void(bool *)> check = [](bool *ptr)
    { 
        std::cout << ptr << std::endl;
        *ptr = true; 
    };

    // Configure some tasks to execute
    
    std::vector<VoidFunctionWrapper> tasks_to_execute;
    std::deque<bool> check_list(5, false);
    for (size_t i_check = 0; i_check < check_list.size(); i_check++)
    {
        VoidFunctionWrapper temp_func = VoidFunctionWrapper(check, &(check_list[i_check]));
        tasks_to_execute.push_back(std::move(temp_func));
    }
    
    for (size_t i_check = 0; i_check < check_list.size(); i_check++)
    {
        tasks_to_execute[i_check].get_wrapped_function()();
    }

    for (size_t i_check = 0; i_check < check_list.size(); i_check++)
    {
        std::cout << check_list[i_check] << " ";
    }
    std::cout << std::endl;
}

The result is:

0x7f9506008804
0x7f9506008804
0x7f9506008804
0x7f9506008804
0x7f9506008804
0 0 0 0 1 

I noticed that to pass a &(check_list[i_check]) may be incorrect when using the capture list [&]. This r-value &(check_list[i_check]) may exceed the survival period when the final wrapped function is called. I am really confused about the behaviour of my code. I am expecting a "dangling pointer" when trying to call the wrapped function, however, this does not happen.

To make it short, I am wondering how the r-value &(check_list[i_check]) captured in the lambda expression works, making all VoidFunctionWrapper instances change the same variable's value.


Solution

  • You do have dangling references, and the use of them has undefined behaviour. What you are surprised by is the particular symptoms of the undefined behaviour.

    C++ is not a safe language. Experiencing undefined behaviour does not mean the program crashes, it means that you cannot rely on the rules of C++ to predict the program's behaviour. Anything can happen.