Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert std::future<T> to std::future<void>?

I have a situation where I have a std::future<some_type> resulting from a call to API A, but need to supply API B with a std::future<void>:

std::future<some_type> api_a();

void api_b(std::future<void>& depend_on_this_event);

In the absence of proposed functionality such as .then() or when_all(), is there any efficient way to throw away the value attached to a std::future<T> and be left only with the underlying std::future<void> representing the event's completion?

Something like the following could work but would be potentially inefficient:

auto f = api_a();
f.wait();
auto void_f = std::async(std::launch::defer, []{});
api_b(void_f);
like image 233
Jared Hoberock Avatar asked Apr 13 '15 22:04

Jared Hoberock


2 Answers

The best you can get is probably this:

auto f = api_a();
auto void_f = std::async(std::launch::deferred,[fut = std::move(f)]{ fut.wait();});
api_b(void_f);
like image 124
sbabbi Avatar answered Oct 08 '22 05:10

sbabbi


template<class U>
struct convert_future_t {
  template<class T>
  std::future<U> operator()( std::future<T>&& f ) const {
    return std::async(std::launch::deferred,
      [f=std::move(f)]()->U{ return f.get(); }
    );
  }
}
template<>
struct convert_future_t<void> {
  template<class T>
  std::future<void> operator()( std::future<T>&& f ) const {
    return std::async(std::launch::deferred,
      [f=std::move(f)]()->void{ f.get(); }
    );
  }
}
template<class U, class T>
std::future<U> convert_future( std::future<T>&& f ) {
  return convert_future_t<U>{}(std::move(f));
}

this is a generic version of @sbabbi's answer.

api_b( convert_future<void>( api_a() ) );

that allows for any target and dest type to work transparently.

The large downside to this approach is that the resulting future is a deferred future wrapping a (possibly async) future, which means that .wait_for() and .ready() APIs do not work like async futures do. The returned future will never be ready until waited.

So we can improve this marginally:

template<class T>
struct ready_future_t {
  template<class...Us>
  std::future<T> operator()( Us&&...us ) const {
    std::promise<T> p;
    p.set_value(T(std::forward<Us>(us)...));
    return p.get_future();
  }
};
template<>
struct ready_future_t<void> {
  using T=void;
  // throws away the Us&&...s
  template<class...Us>
  std::future<T> operator()( Us&&...us ) const {
    std::promise<T> p;
    p.set_value();
    return p.get_future();
  }
};
template<class T, class...Us>
std::future<T> ready_future(Us&&...us){
  return ready_future_t<T>{}(std::forward<Us>(us)...);
}
template<class U>
struct convert_future_t {
  template<class T>
  std::future<U> operator()( std::future<T>&& f ) const {
    if (f.wait_for(0ms)==std::future_status::ready)
      return ready_future<U>(f.get());
    return std::async(std::launch::deferred,
      [f=std::move(f)]()->U{ return f.get(); }
    );
  }
};
template<>
struct convert_future_t<void> {
  template<class T>
  std::future<void> operator()( std::future<T>&& f ) const {
    if (f.wait_for(0ms)==std::future_status::ready)
      return ready_future<void>();
    return std::async(std::launch::deferred,
      [f=std::move(f)]()->void{ f.get(); }
    );
  }
};

where at least if the future was already ready by the time we converted it, the returned future is also ready.

live example

like image 1
Yakk - Adam Nevraumont Avatar answered Oct 08 '22 05:10

Yakk - Adam Nevraumont