Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Is it still valid when the variable passed to `std::async` is out of scope?

Is the code snippet below legal? What worries me is when factorial is invoked the fut_num may be already out of scope.

#include <future>
#include <vector>
#include <iostream>

//int factorial(std::future<int> fut) //works, because there is a move constructor
int factorial(std::future<int>&& fut)
{
    int res = 1;
    int num = fut.get();

    for(int i=num; i>1; i--)
    {
        res *= i;
    }

    return res;
}

int main()
{
    std::promise<int> prs;
    std::vector<std::future<int>> vec;

    {
        std::future<int> fut_num{prs.get_future()};
        vec.push_back(std::async(std::launch::async, factorial, std::move(fut_num)));
    }  //`fut_num` is out of range now.
    
    prs.set_value(5);

    for(auto& fut: vec)
    {
        std::cout << fut.get() << std::endl;
    }
}

And the same question about similar code snippet:

#include <future>
#include <vector>
#include <iostream>

//int factorial(std::future<int> fut) //works, because there is a move constructor
int factorial(std::future<int>& fut)
{
    int res = 1;
    int num = fut.get();

    for(int i=num; i>1; i--)
    {
        res *= i;
    }

    return res;
}

int main()
{
    std::promise<int> prs;
    std::vector<std::future<int>> vec;

    {
        std::future<int> fut_num{prs.get_future()};
        vec.push_back(std::async(std::launch::async, factorial, std::ref(fut_num)));
    }  //`fut_num` is out of range now.
    
    prs.set_value(5);

    for(auto& fut: vec)
    {
        std::cout << fut.get() << std::endl;
    }
}

My two cents about these code snippets:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

1.The former code snippet is legal, since std::async copies std::move(fut_num)(i.e. std::move(fut_num) is passed by value to std::async). So there is a local fut_num when fcatorical is called.

2.The latter one is illegal, since fut_num is passed as a reference to std::async. When fut_num is out of scope, it’s illegal to call functions which uses fut_num.

>Solution :

The first one is fine, the second one is not.


std::async with std::launch::async uses the same procedure of invoking the thread function as the constructor of std::thread does. Both effectively execute

std::invoke(auto(std::forward<F>(f)), auto(std::forward<Args>(args))...);

in the new thread, but with the auto(...) constructions executing in the context of the calling thread.

Here F/Args... are the (other) template parameters with corresponding forwarding-reference function parameters F&& f/Args&&... args and auto has the new C++23 meaning which creates a prvalue/temporary of the decayed argument’s type from the argument.

See [futures.async]/4.1.

This means there will be unnamed copies constructed for all arguments which live in the new thread until after the thread function invocation returns.

So with std::move(fut_num), there will actually be another std::future<int> object which will be move-constructed from fut_num and live until the thread ends execution. factorial will be passed a reference to this unnamed object.


With std::ref(fut_num) you are explicitly by-passing this mechanism protecting you from passing references to the objects in the constructing thread.

The constructor will still make a decayed copy of type std::reference_wrapper<std::future<int>> from the argument, but that object will just contain a reference referring to fut_num in the main thread.

std::invoke will then unwrap the std::reference_wrapper before passing to factorial and the fut argument will refer to fut_num in the main thread, which is then destroyed without any synchronization, causing undefined behavior as it is also accessed in the factorial function.


It either case it doesn’t matter whether factorial takes the argument by-reference or by-value. Nothing about the above reasoning changes.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading