Разница между std :: result_of и decltype

100

У меня проблемы с пониманием необходимости std::result_ofв C ++ 0x. Если я правильно понял, result_ofиспользуется для получения результирующего типа вызова объекта функции с определенными типами параметров. Например:

template <typename F, typename Arg>
typename std::result_of<F(Arg)>::type
invoke(F f, Arg a)
{
    return f(a);
}

Я не вижу разницы в следующем коде:

template <typename F, typename Arg>
auto invoke(F f, Arg a) -> decltype(f(a)) //uses the f parameter
{
    return f(a);
}

или

template <typename F, typename Arg>
auto invoke(F f, Arg a) -> decltype(F()(a)); //"constructs" an F
{
    return f(a);
}

Единственная проблема, которую я вижу с этими двумя решениями, заключается в том, что нам нужно либо:

  • иметь экземпляр функтора, чтобы использовать его в выражении, переданном в decltype.
  • знать определенный конструктор для функтора.

Прав ли я, думая, что единственная разница между decltypeи в result_ofтом, что для первого требуется выражение, а для второго - нет?

Люк Турель
источник

Ответы:

86

result_ofбыл представлен в Boost , а затем включен в TR1 и, наконец, в C ++ 0x. Поэтому result_ofимеет преимущество обратной совместимости (с подходящей библиотекой).

decltype это совершенно новая вещь в C ++ 0x, не ограничивается только возвращаемым типом функции и является особенностью языка.


В любом случае, в gcc 4.5 result_ofреализовано с точки зрения decltype:

  template<typename _Signature>
    class result_of;

  template<typename _Functor, typename... _ArgTypes>
    struct result_of<_Functor(_ArgTypes...)>
    {
      typedef
        decltype( std::declval<_Functor>()(std::declval<_ArgTypes>()...) )
        type;
    };
Kennytm
источник
4
Насколько я понимаю decltypeуродливее, но и мощнее. result_ofможет использоваться только для вызываемых типов и требует типов в качестве аргументов. Например, вы не можете использовать result_ofздесь: template <typename T, typename U> auto sum( T t, U u ) -> decltype( t + u );if аргументы могут быть арифметическими типами (нет Fтакой функции , которую вы можете определить F(T,U)для представления t+u. Для пользовательских типов вы могли бы. Таким же образом (я действительно не играл с этим), я полагаю что вызовы методов-членов могут быть трудными result_ofбез использования связующих или лямбд
Дэвид Родригес - dribeas
2
Одно замечание: decltype нужны аргументы для вызова функции с AFAIK, поэтому без result_of <> неудобно получить тип, возвращаемый шаблоном, не полагаясь на аргументы, имеющие допустимые конструкторы по умолчанию.
Роберт Мейсон
3
@RobertMason: Эти аргументы можно получить с std::declvalпомощью кода, который я показал выше. Конечно, это некрасиво :)
kennytm 01
1
@ DavidRodríguez-dribeas В вашем комментарии есть незакрытые круглые скобки, которые открываются с "(есть" :(
Navin
1
Еще одно замечание: result_ofвспомогательный тип и его вспомогательный тип result_of_tустарели в C ++ 17 в пользу invoke_resultи invoke_result_t, что смягчает некоторые ограничения первого. Они перечислены внизу en.cppreference.com/w/cpp/types/result_of .
sigma
11

Если вам нужен тип чего-то, что не похоже на вызов функции, std::result_ofпросто не применяется. decltype()может дать вам тип любого выражения.

Если мы ограничимся только различными способами определения типа возвращаемого значения вызова функции (между std::result_of_t<F(Args...)>и decltype(std::declval<F>()(std::declval<Args>()...)), тогда будет разница.

std::result_of<F(Args...) определяется как:

Если выражение INVOKE (declval<Fn>(), declval<ArgTypes>()...)правильно сформировано при обработке как неоцененный операнд (раздел 5), тип typedef члена должен называть этот тип, в decltype(INVOKE (declval<Fn>(), declval<ArgTypes>()...)); противном случае тип члена не должен быть.

В этом вся разница между result_of<F(Args..)>::typeи . Использование / напрямую, помимо того, что он немного длиннее для ввода, допустимо только в том случае, если он вызывается напрямую (тип объекта функции или функция или указатель функции). дополнительно поддерживает указатели на функции-члены и указатели на данные-члены.decltype(std::declval<F>()(std::declval<Args>()...)INVOKEdeclvaldecltypeFresult_of

Первоначально использование declval/ decltypeгарантировало дружественное к SFINAE выражение, тогда как std::result_ofмогло бы дать вам серьезную ошибку вместо ошибки вывода. Это было исправлено в C ++ 14: std::result_ofтеперь требуется, чтобы он был совместим с SFINAE (благодаря этой статье ).

Таким образом, на соответствующем компиляторе C ++ 14 std::result_of_t<F(Args...)>он строго превосходит. Это ясно, короче, и правильно поддерживает более Fs .


Если, то есть, вы не используете его в контексте, в котором вы не хотите разрешать указатели на члены, он std::result_of_tбудет успешным в случае, когда вы, возможно, захотите, чтобы он потерпел неудачу.

За исключениями. Хотя он поддерживает указатели на члены, result_ofне будет работать, если вы попытаетесь создать экземпляр недопустимого идентификатора типа . Они могут включать функцию, возвращающую функцию или принимающую абстрактные типы по значению. Например:

template <class F, class R = result_of_t<F()>>
R call(F& f) { return f(); }

int answer() { return 42; }

call(answer); // nope

Правильное использование было бы правильным result_of_t<F&()>, но это деталь, о которой не нужно помнить decltype.

Барри
источник
Правильно ли использование не ссылочного типа Tи функции ? template<class F> result_of_t<F&&(T&&)> call(F&& f, T&& arg) { return std::forward<F>(f)(std::move(arg)); }result_of_t
Zizheng Tai
Кроме того , если аргумент , который мы переходим к fэто const T, мы должны использовать result_of_t<F&&(const T&)>?
Zizheng Tai