Diferencia entre std :: result_of y decltype

100

Tengo algunos problemas para comprender la necesidad de std::result_ofC ++ 0x. Si he entendido bien, result_ofse usa para obtener el tipo resultante de invocar un objeto de función con ciertos tipos de parámetros. Por ejemplo:

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

Realmente no veo la diferencia con el siguiente código:

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

o

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

El único problema que puedo ver con estas dos soluciones es que necesitamos:

  • tener una instancia del functor para usarlo en la expresión pasada a decltype.
  • conocer un constructor definido para el functor.

¿Estoy en lo cierto al pensar que la única diferencia entre decltypey result_ofes que el primero necesita una expresión mientras que el segundo no?

Luc Touraille
fuente

Respuestas:

86

result_ofse introdujo en Boost y luego se incluyó en TR1 , y finalmente en C ++ 0x. Por lo tanto, result_oftiene la ventaja de que es compatible con versiones anteriores (con una biblioteca adecuada).

decltype es algo completamente nuevo en C ++ 0x, no se restringe solo al tipo de retorno de una función y es una característica del lenguaje.


De todos modos, en gcc 4.5, result_ofse implementa en términos 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
fuente
4
Por lo que tengo entendido, decltypees más feo pero también más poderoso. result_ofsolo se puede usar para tipos que son invocables y requiere tipos como argumentos. Por ejemplo, no puede usar result_ofaquí: template <typename T, typename U> auto sum( T t, U u ) -> decltype( t + u );si los argumentos pueden ser tipos aritméticos (no hay una función Ftal que pueda definir F(T,U)para representar t+u. Para tipos definidos por el usuario, podría hacerlo. De la misma manera (realmente no he jugado con eso) imagino que las llamadas a métodos de miembros pueden ser difíciles de realizar result_ofsin usar carpetas o lambdas
David Rodríguez - dribeas
2
Una nota, decltype necesita argumentos para llamar a la función con AFAIK, por lo que sin result_of <> es incómodo obtener el tipo devuelto por una plantilla sin depender de que los argumentos tengan constructores predeterminados válidos.
Robert Mason
3
@RobertMason: Esos argumentos se pueden recuperar usando std::declval, como el código que he mostrado arriba. Por supuesto, esto es feo :)
kennytm
1
@ DavidRodríguez-dribeas Tu comentario tiene paréntesis sin cerrar que se abren con "(hay" :(
Navin
1
Otra nota: result_ofy su tipo de ayuda result_of_testán en desuso a partir de C ++ 17 a favor de invoke_resulty invoke_result_t, aliviando algunas restricciones del primero. Estos se enumeran al final de en.cppreference.com/w/cpp/types/result_of .
sigma
11

Si necesita el tipo de algo que no es algo así como una llamada de función, std::result_ofsimplemente no se aplica. decltype()puede darte el tipo de cualquier expresión.

Si nos limitamos a las diferentes formas de determinar el tipo de retorno de una llamada de función (entre std::result_of_t<F(Args...)>y decltype(std::declval<F>()(std::declval<Args>()...)), entonces hay una diferencia.

std::result_of<F(Args...) Se define como:

Si la expresión INVOKE (declval<Fn>(), declval<ArgTypes>()...)está bien formada cuando se trata como un operando no evaluado (cláusula 5), ​​el tipo de miembro typedef nombrará el tipo; de lo decltype(INVOKE (declval<Fn>(), declval<ArgTypes>()...)); contrario, no habrá ningún tipo de miembro.

La diferencia entre result_of<F(Args..)>::typey decltype(std::declval<F>()(std::declval<Args>()...)se trata de eso INVOKE. Usar declval/ decltypedirectamente, además de ser un poco más largo de escribir, solo es válido si Fse puede llamar directamente (un tipo de objeto de función o una función o un puntero de función). result_ofademás, admite punteros a funciones de miembros y punteros a datos de miembros.

Inicialmente, usar declval/ decltypegarantizaba una expresión compatible con SFINAE, mientras que std::result_ofpodría generar un error grave en lugar de un error de deducción. Eso se ha corregido en C ++ 14: std::result_ofahora se requiere que sea compatible con SFINAE (gracias a este documento ).

Entonces, en un compilador conforme a C ++ 14, std::result_of_t<F(Args...)>es estrictamente superior. Es más claro, más corto y correctamente admite más Fs .


A menos que, es decir, lo esté utilizando en un contexto en el que no desea permitir punteros a los miembros, por std::result_of_tlo que tendría éxito en un caso en el que desee que falle.

Con excepciones. Si bien admite punteros a miembros, result_ofno funcionará si intenta crear una instancia de un identificador de tipo no válido . Estos incluirían una función que devuelve una función o que toma tipos abstractos 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

El uso correcto habría sido result_of_t<F&()>, pero ese es un detalle que no tiene que recordar decltype.

Barry
fuente
Para un tipo de no referencia Ty una función template<class F> result_of_t<F&&(T&&)> call(F&& f, T&& arg) { return std::forward<F>(f)(std::move(arg)); }, ¿es result_of_tcorrecto el uso de ?
Zizheng Tai
Además, si el argumento al que pasamos fes a const T, ¿deberíamos usar result_of_t<F&&(const T&)>?
Zizheng Tai