Différence entre std :: result_of et decltype

100

J'ai du mal à comprendre la nécessité de std::result_ofC ++ 0x. Si j'ai bien compris, result_ofest utilisé pour obtenir le type résultant de l'appel d'un objet fonction avec certains types de paramètres. Par exemple:

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

Je ne vois pas vraiment la différence avec le code suivant:

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

ou

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

Le seul problème que je vois avec ces deux solutions est que nous devons soit:

  • avoir une instance du foncteur pour l'utiliser dans l'expression passée à decltype.
  • connaître un constructeur défini pour le foncteur.

Ai-je raison de penser que la seule différence entre decltypeet result_ofest que le premier a besoin d'une expression alors que le second n'en a pas?

Luc Touraille
la source

Réponses:

86

result_ofa été introduit dans Boost , puis inclus dans TR1 , et enfin dans C ++ 0x. A donc result_ofun avantage qui est rétrocompatible (avec une bibliothèque adaptée).

decltype est une chose entièrement nouvelle dans C ++ 0x, ne se limite pas uniquement au type de retour d'une fonction, et est une fonctionnalité du langage.


Quoi qu'il en soit, sur gcc 4.5, result_ofest implémenté en termes de 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
la source
4
Autant que je decltypesache, c'est plus laid mais aussi plus puissant. result_ofne peut être utilisé que pour les types appelables et nécessite des types comme arguments. Par exemple, vous ne pouvez pas utiliser result_ofici: template <typename T, typename U> auto sum( T t, U u ) -> decltype( t + u );si les arguments peuvent être des types arithmétiques (il n'y a pas de fonction Ftelle que vous pouvez définir F(T,U)pour représenter t+u. Pour les types définis par l'utilisateur, vous pouvez. De la même manière (je n'ai pas vraiment joué avec) j'imagine que les appels aux méthodes membres peuvent être difficiles à faire result_ofsans utiliser de liants ou de lambdas
David Rodríguez - dribeas
2
Une note, decltype a besoin d'arguments pour appeler la fonction avec AFAIK, donc sans result_of <> il est difficile d'obtenir le type retourné par un modèle sans se fier aux arguments ayant des constructeurs par défaut valides.
Robert Mason
3
@RobertMason: Ces arguments peuvent être récupérés en utilisant std::declval, comme le code que j'ai montré ci-dessus. Bien sûr, c'est moche :)
kennytm
1
@ DavidRodríguez-dribeas Votre commentaire a des parenthèses non fermées qui s'ouvrent avec "(il y a" :(
Navin
1
Une autre note: result_ofet son type d'assistance result_of_tsont obsolètes à partir de C ++ 17 en faveur de invoke_resultet invoke_result_t, allégeant certaines restrictions du premier. Ceux-ci sont répertoriés au bas de en.cppreference.com/w/cpp/types/result_of .
sigma
11

Si vous avez besoin du type de quelque chose qui ne ressemble pas à un appel de fonction, std::result_ofcela ne s'applique tout simplement pas. decltype()peut vous donner le type de n'importe quelle expression.

Si nous nous limitons aux différentes manières de déterminer le type de retour d'un appel de fonction (entre std::result_of_t<F(Args...)>et decltype(std::declval<F>()(std::declval<Args>()...)), alors il y a une différence.

std::result_of<F(Args...) est défini comme:

Si l'expression INVOKE (declval<Fn>(), declval<ArgTypes>()...)est bien formée lorsqu'elle est traitée comme un opérande non évalué (clause 5), le type typedef membre doit nommer le typedecltype(INVOKE (declval<Fn>(), declval<ArgTypes>()...)); sinon, il n'y aura pas de type de membre.

La différence entre result_of<F(Args..)>::typeet decltype(std::declval<F>()(std::declval<Args>()...)est tout à propos de cela INVOKE. L'utilisation de declval/ decltypedirectement, en plus d'être un peu plus longue à taper, n'est valide que si elle Fest directement appelable (un type d'objet fonction ou une fonction ou un pointeur de fonction).result_ofprend également en charge les pointeurs vers les fonctions des membres et les pointeurs vers les données des membres.

Initialement, utiliser declval/ decltypegaranti une expression compatible SFINAE, alors que cela std::result_ofpourrait vous donner une erreur matérielle au lieu d'un échec de déduction. Cela a été corrigé en C ++ 14: std::result_ofdoit maintenant être compatible avec SFINAE (grâce à ce papier ).

Donc, sur un compilateur C ++ 14 conforme, std::result_of_t<F(Args...)>c'est strictement supérieur. C'est plus clair, plus court et correctement prend en charge plus de Fs .


À moins que vous ne l'utilisiez dans un contexte où vous ne souhaitez pas autoriser les pointeurs vers les membres, cela std::result_of_tréussirait dans un cas où vous pourriez souhaiter qu'il échoue.

Sauf exceptions. Bien qu'il prenne en charge les pointeurs vers les membres, result_ofne fonctionnera pas si vous essayez d'instancier un type-id non valide . Celles-ci incluraient une fonction renvoyant une fonction ou prenant des types abstraits par valeur. Ex.:

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

int answer() { return 42; }

call(answer); // nope

L'utilisation correcte aurait été result_of_t<F&()>, mais c'est un détail dont vous n'avez pas à vous souvenir decltype.

Barry
la source
Pour un type Tet une fonction sans référence template<class F> result_of_t<F&&(T&&)> call(F&& f, T&& arg) { return std::forward<F>(f)(std::move(arg)); }, est-ce que l'utilisation de est result_of_tcorrecte?
Zizheng Tai
De plus, si l'argument auquel nous passons fest a const T, devons-nous l'utiliser result_of_t<F&&(const T&)>?
Zizheng Tai