Какая разница между result_of <F(Args...> и decltype <f(args...)> ?
Я вижу, что std::async
задается следующим образом:
template <class F, class... Args> // copied out of the standard
future<typename result_of<F(Args...)>::type>
async(F&& f, Args&&... args);
Я ожидал, что это будет объявлено так:
template <class F, class... Args>
auto async(F&& f, Args&&... args) ->
future<decltype(forward<F>(f)(forward<Args>(args)...)>;
Будет ли это эквивалентно, или есть способ, которым использование result_of
предпочтительнее использования decltype
? (Я понимаю, что result_of
работает с типами, а decltype
работает с выражениями.)
Ответы
Ответ 1
Ваша версия не работает, например, указатели на участников. Более близкая, но все же не точная версия:
template <class F, class... Args>
auto async(F&& f, Args&&... args)
-> future<decltype( ref(f)(forward<Args>(args)...) )>;
Единственная разница, остающаяся с std::result_of
состоит в том, что это переводит функтор как lvalue (проблема, которую также разделяет ваша версия). Другими словами, результатом такого вызова (через std::reference_wrapper<F>
) является typename std::result_of<F&(Args...)>::type
.
Это неудобная ситуация, когда несколько компонентов стандартной библиотеки (для обозначения нескольких, в дополнение к тем, которые мы только что наблюдали: std::thread
, std::bind
, std::function
), указаны в терминах неуловимого INVOKE (f, a0, a1,..., aN), которое не является точно эквивалентным f(a0, a1,... aN)
. Поскольку std::result_of
является одним из этих компонентов и фактически служит для вычисления типа результата INVOKE, это несоответствие, которое вы замечаете.
Поскольку нет std::invoke
который приходит в тандеме с чертой типа std::result_of
я считаю, что последнее полезно только для описания, например, типов возврата соответствующих компонентов стандартной библиотеки, когда ваш код вызывает их. Если вам нужен сжатый и самодокументированный способ записи, например, тип возвращаемого значения (очень decltype
цель для удобочитаемости, по сравнению с decltype
везде), то я рекомендую вам написать свой собственный псевдоним:
template<typename F, typename... A>
using ResultOf = decltype( std::declval<F>()(std::declval<A>()...) );
(Если вы хотите, чтобы псевдоним использовался как ResultOf<F(A...)>
вместо ResultOf<F, A...>
вам понадобится немного машинного оборудования для сопоставления шаблонов над сигнатурой функции.)
Дополнительным преимуществом этого псевдонима является то, что он является SFINAE дружественным, в отличие от std::result_of
. Да, это еще одна из его недостатков. (Чтобы быть справедливым, хотя это было изменено для предстоящего Стандарта, и реализация уже подходит).
Вы бы не пропустили ничего, если бы использовали такую черту, потому что вы можете адаптировать указатели к членам благодаря std::mem_fn
.
Ответ 2
Никакой разницы с функциональной точки зрения. Тем не менее, версия decltype
использует тип trailing-return-type, то есть одно отличие от точки программирования.
В С++ 11 std::result_of
не является абсолютно необходимым, вместо этого можно использовать decltype
, как и вы использовали. Но std::result_of
обратно совместим с сторонними библиотеками (более старыми), такими как Boost, у которого есть result_of
. Однако я не вижу большого преимущества.
Лично я предпочитаю использовать decltype
поскольку он более мощный и работает с любыми объектами, тогда как result_of
работает только с вызываемыми объектами. Поэтому, если я использую decltype
, я могу использовать его везде. Но с result_of
, я должен иногда переключаться на decltype
(то есть, когда объект не вызываем). См. Это в github, где я использовал decltype
в обратном типе всех функций.
Ответ 3
У вас уже было различие в ваших вопросах: "Я понимаю, что result_of
работает с типами, а decltype
работает с выражениями".
Оба они обеспечивают ту же функциональность (std::result_of
даже реализуется с точки зрения decltype
настоящее время), а разница для вашего примера в основном отсутствует, потому что у вас уже есть необходимые переменные для построения выражения.
Тем не менее, разница сводится к синтаксическому сахару, когда у вас есть только типы. Рассматривать:
typename std::result_of< F( A, B, C ) >::type
против
decltype( std::declval<F>()( std::declval<A>(), std::declval<B>(), std::declval<C>() )
И просто помните, что std::result_of
является вариантом в этих случаях.
Обратите внимание, что есть также случаи, когда decltype
может использоваться таким образом, что std::result_of
не может. Рассмотрим decltype( a + b )
, вы не можете найти F
чтобы создать эквивалентный std::result_of< F( A, B ) >
насколько мне известно.