Differenza tra std :: result_of e decltype


100

Ho qualche problema a capire la necessità di std::result_ofin C ++ 0x. Se ho capito bene, result_ofviene utilizzato per ottenere il tipo risultante di invocare un oggetto funzione con determinati tipi di parametri. Per esempio:

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

Non vedo davvero la differenza con il seguente codice:

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

L'unico problema che posso vedere con queste due soluzioni è che dobbiamo:

  • avere un'istanza del funtore per usarlo nell'espressione passata a decltype.
  • conoscere un costruttore definito per il funtore.

Ho ragione nel pensare che l'unica differenza tra decltypee result_ofè che il primo necessita di un'espressione mentre il secondo no?

Risposte:


86

result_ofè stato introdotto in Boost , quindi incluso in TR1 e infine in C ++ 0x. result_ofHa quindi un vantaggio compatibile con le versioni precedenti (con una libreria adatta).

decltype è una cosa completamente nuova in C ++ 0x, non si limita solo al tipo di ritorno di una funzione ed è una caratteristica del linguaggio.


Ad ogni modo, su gcc 4.5, result_ofè implementato in termini di 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;
    };

4
Per quanto ho capito decltypeè più brutto ma anche più potente. result_ofpuò essere utilizzato solo per i tipi richiamabili e richiede i tipi come argomenti. Ad esempio, non puoi usare result_ofqui: template <typename T, typename U> auto sum( T t, U u ) -> decltype( t + u );se gli argomenti possono essere tipi aritmetici (non esiste una funzione Ftale che tu possa definire F(T,U)per rappresentare t+u. Per i tipi definiti dall'utente potresti. Allo stesso modo (non ci ho davvero giocato) immagino che le chiamate ai metodi dei membri potrebbero essere difficili da fare result_ofsenza utilizzare leganti o lambda
David Rodríguez - dribeas

2
Una nota, decltype necessita di argomenti per chiamare la funzione con AFAIK, quindi senza result_of <> è scomodo ottenere il tipo restituito da un modello senza fare affidamento sugli argomenti che hanno costruttori predefiniti validi.
Robert Mason

3
@RobertMason: questi argomenti possono essere recuperati usando std::declval, come il codice che ho mostrato sopra. Certo, questo è brutto :)
kennytm

1
@ DavidRodríguez-dribeas Il tuo commento ha parentesi non chiuse che si aprono con "(c'è" :(
Navin

1
Un'altra nota: result_ofe il suo tipo di supporto result_of_tsono deprecati a partire da C ++ 17 a favore di invoke_resulte invoke_result_t, alleviando alcune restrizioni del primo. Questi sono elencati in fondo a en.cppreference.com/w/cpp/types/result_of .
sigma

11

Se hai bisogno del tipo di qualcosa che non è qualcosa come una chiamata di funzione, std::result_ofsemplicemente non si applica. decltype()può darti il ​​tipo di qualsiasi espressione.

Se ci limitiamo solo ai diversi modi per determinare il tipo di ritorno di una chiamata di funzione (tra std::result_of_t<F(Args...)>e decltype(std::declval<F>()(std::declval<Args>()...)), allora c'è una differenza.

std::result_of<F(Args...) è definito come:

Se l'espressione INVOKE (declval<Fn>(), declval<ArgTypes>()...)è ben formata quando trattata come un operando non valutato (clausola 5), ​​il tipo di membro typedef deve denominare il tipo decltype(INVOKE (declval<Fn>(), declval<ArgTypes>()...)); altrimenti, non ci sarà alcun tipo di membro.

La differenza tra result_of<F(Args..)>::typee decltype(std::declval<F>()(std::declval<Args>()...)sta tutto in questo INVOKE. L'uso diretto di declval/ decltype, oltre ad essere un po 'più lungo da digitare, è valido solo se Fè direttamente richiamabile (un tipo di oggetto funzione o una funzione o un puntatore a funzione). result_ofsupporta inoltre puntatori alle funzioni dei membri e puntatori ai dati dei membri.

Inizialmente, l'utilizzo di declval/ decltypegarantiva un'espressione amichevole per SFINAE, mentre std::result_ofpotrebbe darti un errore difficile invece di un errore di deduzione. Ciò è stato corretto in C ++ 14: std::result_ofora è necessario che sia compatibile con SFINAE (grazie a questo documento ).

Quindi su un compilatore C ++ 14 conforme, std::result_of_t<F(Args...)>è strettamente superiore. È più chiaro, più breve e correttamente supporta più Fs .


A meno che, cioè, non lo si utilizzi in un contesto in cui non si desidera consentire i puntatori ai membri, quindi std::result_of_tavrebbe successo in un caso in cui si potrebbe desiderare che fallisca.

Con eccezioni. Sebbene supporti i puntatori ai membri, result_ofnon funzionerà se provi a creare un'istanza di un tipo-id non valido . Questi includerebbero una funzione che restituisce una funzione o prende tipi astratti per valore. Ex.:

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

int answer() { return 42; }

call(answer); // nope

L'utilizzo corretto sarebbe stato result_of_t<F&()>, ma questo è un dettaglio con cui non devi ricordare decltype.


Per un tipo non di riferimento Te una funzione template<class F> result_of_t<F&&(T&&)> call(F&& f, T&& arg) { return std::forward<F>(f)(std::move(arg)); }, l'uso di è result_of_tcorretto?
Zizheng Tai,

Inoltre, se l'argomento a cui passiamo fè a const T, dovremmo usare result_of_t<F&&(const T&)>?
Zizheng Tai,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.