Какая разница между 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 ) > насколько мне известно.