We know three ways of associating a task with a std::future. They are std::async, std::packaged_task and std::promise.

The benefit of the packaged task over std::async is to decouple the creation of the future with the execution of the task.

The benefit of the promise over std::async and std::packaged_task is that the promise can communicate a value to the future at a different time than at the end of a function call. Let me explain what I mean with this.

Differences

std::async

The std::async takes a function as an argument and returns a future. The future has the same template parameter as the return type of the function. When the function returns, its return value sets the future. Therefore, the std::async communicates the value at the end of the function call. For example:

int function(double);

int main()
{
    ...
    std::future<int> future = std::async(function, 2.5);
    ...
}

The std::async communicates the value to the future at the end of function execution.

std::packaged_task

The constructor of the std::packaged_task takes a function as an argument and creates a packaged task object. The template parameter of the packaged task is the signature of the function.

The .get_future() member function of the object creates a future. The future has the same template parameter as the return type of the function

The execution of a packaged task, which forwards arguments to the function and runs it, sets the return value of the function to the future. Therefore again, the std::packaged_task communicates the value at the end of the function call. For example:

int function(double);

int main()
{
    ...
    std::packaged_task< int(double) > task(function);
    std::future<int> future = task.get_future();
    task(2.5);
    ...
}

task(2.5) executes function(2.5) and sets the return value of the latter call to the future. The std::packaged_task communicates the value to the future at the end of function execution.

std::promise

There are no such restrictions with std::promise. The promise can explicitly set a value to a future anytime and not only at the end of a function call. Let’s look at an example of such behavior.

Manager and mechanic

Imagine a car repair shop. A customer would like to buy a car. She would like to be convinced that the engine of the car is OK. The manager of the shop instructs the mechanic to inspect the engine. As soon as it is clear that the engine is OK, the manager can negotiate a contract with the customer.

Let’s model this story with a program. We have a class which represents a car.

class Car 
{
public:
    Car() 
        :hood(true), electricity(true),
         exhaust(true), engine(true)
    { }
    
    void remove_the_hood()
    { hood = false; }
    
    void add_the_hood()
    { hood = true; }

    void disconnect_electricity()
    { electricity = false; }

    void connect_electricity()
    { electricity = true; }

    void remove_exhaust_system()
    { exhaust = false; }
    
    void add_exhaust_system()
    { exhaust = true; }
    
    bool is_engine_ok() const
    { return engine; }

private:
    bool hood;
    
    bool electricity;
    
    bool exhaust;
    
    bool engine;
};

The Car has four data members: hood, electricity, exhaust and engine. They are all represented by a boolean variable. If a data member is true than everything is OK, otherwise something is wrong (the part is broken or missing).

Additionally, there are data functions which can remove/add certain part of the car. The .is_engine_ok() tells us whether the engine is OK.

The next part describes the behavior of the mechanic.

void make_break(int millisec)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(millisec));
}
        
void mechanic(Car& car, std::promise<bool> engine_ok)
{
    car.remove_the_hood();
    car.disconnect_electricity();
    car.remove_exhaust_system();
    
    std::cout << "Mechanic: took a car apart." << std::endl;
    
    engine_ok.set_value(car.is_engine_ok());
    
    std::cout << "Mechanic: engine is ok." << std::endl;
    
    if (car.is_engine_ok())
    {
        make_break(10); 
        car.add_exhaust_system();
        car.connect_electricity();
        car.add_the_hood();
    
        std::cout << "Mechanic: put car back together." << std::endl;
    }
}

The mechanic first removes the hood, electricity and the exhaust system in order to get an access to the engine. Then he communicates the status of the engine to the manager via the promise. If the engine is OK, the mechanic puts the car back together.

The point is that the promise communicates the status of the engine in the middle of the function. This is not possible with std::async or std::packaged_task, because they communicate a value only at the end of the function call.

The last part describes the manager.

void manager()
{
    Car car;
    
    std::cout << "Manager: I would need to know if the car engine is ok."
              << std::endl;
    
    std::promise<bool> promise;
    std::future<bool> answer = promise.get_future();
    
    std::thread thread (mechanic, std::ref(car), std::move(promise));
    thread.detach();
    
    if (answer.get())
    {
        std::cout << "Manager: ensures the client that the engine is ok.\n"
                  << "Manager: negotiates the contract for selling the car."
                  << std::endl;
    }
    else
    {
        std::cout << "Manager: buys a new engine." << std::endl;
    }
    
    make_break(10);
}

int main()
{
    manager();
    return 0;
}

The manager would like to know if the car engine is OK. Therefore, he instructs the mechanic to check the engine. This is done by creating a new thread which represents the mechanic:

std::thread thread (mechanic, std::ref(car), std::move(promise));   

The function mechanic(...) requires two arguments: a car and a promise. The std::ref correctly passes the car as a reference to the mechanic(...). Without std::ref a copy of the car would be passed. Look at Passing arguments to a thread for a detailed explanation.

The promise is not copyable, therefore std::move moves it to the mechanic(...).

After creating and detaching the thread, the manager waits for the answer from the mechanic. If the answer is true – the car engine is OK – then the manager negotiates the contract for selling the car. Otherwise, he buys a new engine.

If you are not familiar with std::promise, look at Promise.

Running the example

The output of the example is:

$ ./promise2
Manager: I would need to know if the car engine is ok.
Mechanic: took a car apart.
Mechanic: engine is ok.
Manager: ensures the client that the engine is ok.
Manager: negotiates the contract for selling the car.
Mechanic: put car back together.

The figure below describes the timeline of the program.

Car and promise

This example shows the difference between the promise and other synchronization mechanisms. The promise communicates the status of the engine in the middle of the function and not at the end.

Summary

The difference between the promise and other synchronization mechanisms is that the promise can communicate the value at a different time than at the end of the function call. We illustrated this difference with the example.