Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decay types before passing to std::result_of

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_ts (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 trues.

like image 924
Zizheng Tai Avatar asked Jun 15 '16 11:06

Zizheng Tai


1 Answers

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.

like image 74
Barry Avatar answered Oct 24 '22 10:10

Barry