Выполнение (возможно, недействительного) обещания

Я пишу многопоточный код и используя обещание/будущее, чтобы вызвать функцию в другом потоке и вернуть ее результат. Для упрощения я полностью удаляю резьбовую часть:

template <typename F>
auto blockingCall(F f) -> decltype(f()) 
{
    std::promise<decltype(f())> promise;

    // fulfill the promise
    promise.set_value(f());

    // block until we have a result
    return promise.get_future().get();
}

Это отлично работает для любой функции, которая возвращает не void. И выражение return, получающее будущее, также работает для void. Но я не могу выполнить обещание, если f является функцией void, потому что:

promise.set_value (е());// ошибка: недопустимое использование выражения void

Есть ли какой-нибудь умный способ установки значения в случае void в строке, или мне нужно просто написать вспомогательную функцию типа call_set_value(promise, f), которая имеет перегрузки для std::promise<R> и std::promise<void>?

Ответы

Ответ 1

A promise - это только один асинхронный поставщик результатов. Вместо promise вы можете использовать packaged_task, который обертывает вызываемый объект, похожий на std::function, за исключением того, что его вызывает доступ к результату через future (и, конечно же, он обрабатывает разницу между void и непустые результаты):

template <typename F>
auto blockingCall(F f) -> decltype(f()) 
{
    std::packaged_task<decltype(f())()> task(std::move(f));

    task();

    // block until we have a result
    return task.get_future().get();
}

N.B. в соответствии с действующим стандартом, этот код будет иметь гонку данных, если task() и task.get_future() произойдут в отдельных потоках (и ваш оригинал будет использовать обещание), поэтому вы должны позвонить get_future(), прежде чем передать задачу другому нить. На практике это должно быть безопасно для реальных реализаций, и там проблема с библиотекой (LWG 2412), чтобы сделать ее действительной в любом случае.

Ответ 2

Да. Функция перегрузки - самое чистое решение:

set(promise, f);

затем реализуем set как перегруженные функции, так как:

template<typename F, typename R>
void set(std::promise<R> & p, F && f) //handle non-void here
{
    p.set_value(f()); 
}

template<typename F>
void set(std::promise<void> & p, F && f)  //handle void here
{
    f();
    p.set_value(); 
}