Типы распада перед переходом в std:: result_of

Как показано на этой странице http://en.cppreference.com/w/cpp/thread/async, одна из сигнатур std::async в С++ 14 была изменена из версии С++ 11

template< class Function, class... Args>
std::future<typename std::result_of<Function(Args...)>::type>
    async( Function&& f, Args&&... args );

к

template< class Function, class... Args>
std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>>
    async( Function&& f, Args&&... args );

Изменения представляют собой std::decay_t (которые удаляют ссылки и cv-квалификаторы и массивы/функции распада в указатели), применяемые к типам функций и аргументов, прежде чем они будут переданы в std::result_of. Я не могу понять, почему распад полезен. Например, для типа функции Fn (возможно, псевдоним типа класса замыкания), передача Fn, Fn&&, const Fn& и т.д., Похоже, дает тот же результат.

Может ли кто-нибудь дать мне конкретный пример, где распад полезен?

ОБНОВЛЕНИЕ: В качестве примера этот код:

#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;
}

выведет четыре true s.

Ответ 1

Изменение происходит в ответ на LWG 2021. Проблема заключается в том, что async (например, bind и т.д.) Decay-копирует все свои аргументы, и поэтому, если вы не использовали decay в возвращаемом типе, вы получите неверный тип возврата, когда это произойдет ref-qualifications и /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

так как все аргументы async - MoveConstructed в свой внутренний объект, вам нужно будет обмануть их, чтобы фактически получить значение аргумента.

Это имеет смысл - async должен хранить свои аргументы где-то, и если вы передаете rvalues, он должен взять на себя ответственность за них. Если он содержит ссылки на rvalue, базовый объект может быть уничтожен. Но как только он сохраняет его как T, он не знает, откуда он пришел с T& или T&& - он просто имеет аргумент lvalue по умолчанию в этой точке.