Типы распада перед переходом в 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 по умолчанию в этой точке.