Diferença entre std :: result_of e decltype

100

Tenho alguns problemas para entender a necessidade de std::result_ofem C ++ 0x. Se bem entendi, result_ofé usado para obter o tipo resultante de invocar um objeto de função com certos tipos de parâmetros. Por exemplo:

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

Eu realmente não vejo a diferença com o seguinte código:

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);
}

O único problema que vejo com essas duas soluções é que precisamos:

  • tem uma instância do functor para usá-lo na expressão passada para decltype.
  • conheça um construtor definido para o functor.

Estou certo em pensar que a única diferença entre decltypee result_ofé que o primeiro precisa de uma expressão enquanto o segundo não?

Luc Touraille
fonte

Respostas:

86

result_offoi introduzido no Boost e depois incluído no TR1 e, finalmente, no C ++ 0x. Portanto, result_oftem a vantagem de ser compatível com versões anteriores (com uma biblioteca adequada).

decltype é algo totalmente novo em C ++ 0x, não se restringe apenas ao tipo de retorno de uma função e é um recurso de linguagem.


De qualquer forma, no gcc 4.5, result_ofé implementado em termos 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
fonte
4
Pelo que entendi decltypeé mais feio, mas também mais poderoso. result_ofsó pode ser usado para tipos que podem ser chamados e requer tipos como argumentos. Por exemplo, você não pode usar result_ofaqui: template <typename T, typename U> auto sum( T t, U u ) -> decltype( t + u );se os argumentos podem ser tipos aritméticos (não há nenhuma função Fque você possa definir F(T,U)para representar t+u. Para tipos definidos pelo usuário, você poderia. Da mesma forma (eu realmente não brinquei com isso) eu imagino que chamadas para métodos de membro podem ser difíceis de fazer result_ofsem usar ligantes ou lambdas
David Rodríguez - dribeas
2
Uma nota, decltype precisa de argumentos para chamar a função com AFAIK, então sem result_of <> é estranho obter o tipo retornado por um modelo sem depender de argumentos com construtores padrão válidos.
Robert Mason
3
@RobertMason: Esses argumentos podem ser recuperados usando std::declval, como o código que mostrei acima. Claro, isso é feio :)
kennytm 01 de
1
@ DavidRodríguez-dribeas Seu comentário tem parênteses não fechados que abrem com "(existe" :(
Navin
1
Outra observação: result_ofe seu tipo auxiliar result_of_tsão obsoletos a partir do C ++ 17 em favor de invoke_resulte invoke_result_t, aliviando algumas restrições do primeiro. Eles estão listados na parte inferior de en.cppreference.com/w/cpp/types/result_of .
sigma
11

Se você precisa do tipo de algo que não é algo como uma chamada de função, std::result_ofsimplesmente não se aplica. decltype()pode fornecer o tipo de qualquer expressão.

Se nos restringirmos apenas às diferentes maneiras de determinar o tipo de retorno de uma chamada de função (entre std::result_of_t<F(Args...)>e decltype(std::declval<F>()(std::declval<Args>()...)), haverá uma diferença.

std::result_of<F(Args...) é definido como:

Se a expressão INVOKE (declval<Fn>(), declval<ArgTypes>()...)for bem formada quando tratada como um operando não avaliado (Cláusula 5), ​​o tipo de typedef de membro deve nomear o tipo de decltype(INVOKE (declval<Fn>(), declval<ArgTypes>()...)); outra forma, não deve haver nenhum tipo de membro.

A diferença entre result_of<F(Args..)>::typee decltype(std::declval<F>()(std::declval<Args>()...)tem tudo a ver com isso INVOKE. Usar declval/ decltypediretamente, além de ser um pouco mais longo para digitar, só é válido se puder Fser chamado diretamente (um tipo de objeto de função ou uma função ou um ponteiro de função). result_ofalém disso, oferece suporte a ponteiros para funções de membros e ponteiros para dados de membros.

Inicialmente, o uso de declval/ decltypegarantiu uma expressão compatível com SFINAE, mas std::result_ofpode gerar um erro grave em vez de uma falha de dedução. Isso foi corrigido no C ++ 14: std::result_ofagora é necessário ser compatível com SFINAE (graças a este artigo ).

Portanto, em um compilador C ++ 14 em conformidade, std::result_of_t<F(Args...)>é estritamente superior. É mais claro, mais curto e corretamente suporta mais Fs .


A menos, isto é, você está usando em um contexto em que não deseja permitir ponteiros para membros, portanto, std::result_of_tseria bem-sucedido em um caso em que você deseja que ele falhe.

Com exceções. Embora suporte ponteiros para membros, result_ofnão funcionará se você tentar instanciar um ID de tipo inválido . Isso incluiria uma função que retorna uma função ou recebe tipos abstratos por valor. Ex.:

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

int answer() { return 42; }

call(answer); // nope

O uso correto teria sido result_of_t<F&()>, mas esse é um detalhe do qual você não precisa se lembrar decltype.

Barry
fonte
Para um tipo sem referência Te uma função template<class F> result_of_t<F&&(T&&)> call(F&& f, T&& arg) { return std::forward<F>(f)(std::move(arg)); }, o uso de result_of_tcorreto?
Zizheng Tai
Além disso, se o argumento que passamos ffor a const T, devemos usar result_of_t<F&&(const T&)>?
Zizheng Tai de