As shown on this page http://en.cppreference.com/w/cpp/thread/async, one of the signature of std::async
in C++14 has been changed from the C++11 version
template< class Function, class... Args>
std::future<typename std::result_of<Function(Args...)>::type>
async( Function&& f, Args&&... args );
to
template< class Function, class... Args>
std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>>
async( Function&& f, Args&&... args );
The changes are the std::decay_t
s (which remove references and cv-qualifiers and decay arrays/functions into pointers) applied on function and argument types before they are passed to std::result_of
. I can't quite see why the decay is useful. For example, for a function type Fn
(maybe a type alias of a closure class), passing Fn
, Fn&&
, const Fn&
etc. all seem to yield the same result.
Can someone give me a concrete example where the decay is useful?
UPDATE: As an example, this code:
#include <iostream>
#include <type_traits>
int main()
{
auto fn = [](auto x) -> int { return x + 1; };
using Fn = decltype(fn);
using FnRef = Fn&;
using FnCRef = const Fn&;
using FnRRef = Fn&&;
std::cout << std::boolalpha
<< std::is_same<int, std::result_of_t<Fn(int)>>::value << '\n'
<< std::is_same<int, std::result_of_t<FnRef(int)>>::value << '\n'
<< std::is_same<int, std::result_of_t<FnCRef(int)>>::value << '\n'
<< std::is_same<int, std::result_of_t<FnRRef(int)>>::value << '\n';
return 0;
}
will print out four true
s.
The change is in response to LWG 2021. The issue is that async
(like bind
, etc.) decay-copies all of its arguments and so if you didn't use decay
in the return type you'd get the wrong return type when it comes to ref-qualifications and or rvalue-ness:
struct F {
int operator()() &;
char operator()() &&;
int operator(int& ) const;
char operator(int&& ) const;
};
auto future = std::async(F{}); // actually gives future<int>, but says
// it gives future<char>?
auto future2 = std::async(F{}, 1); // ditto
since all the arguments to async are MoveConstructed into its internal object, you will need to cleverly wrap them in order to actually achieve rvalue-ness of the arguments.
This makes sense - async
must store its arguments somewhere, and if you pass in rvalues it must take ownership of them. If it holds onto rvalue references, the underlying object could get destroyed. But once it stores it as a T
, it doesn't know where it came from a T&
or a T&&
- it just has a named lvalue argument at that point.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With