Unterschied zwischen std :: result_of und decltype

100

Ich habe einige Probleme, die Notwendigkeit std::result_ofin C ++ 0x zu verstehen . Wenn ich es richtig verstanden habe, result_ofwird es verwendet, um den resultierenden Typ des Aufrufs eines Funktionsobjekts mit bestimmten Arten von Parametern zu erhalten. Beispielsweise:

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

Ich sehe den Unterschied mit dem folgenden Code nicht wirklich:

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

oder

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

Das einzige Problem, das ich bei diesen beiden Lösungen sehen kann, ist, dass wir entweder:

  • Lassen Sie eine Instanz des Funktors in dem an decltype übergebenen Ausdruck verwenden.
  • kennen einen definierten Konstruktor für den Funktor.

Habe ich Recht, wenn ich denke, dass der einzige Unterschied zwischen decltypeund darin result_ofbesteht, dass der erste einen Ausdruck braucht, während der zweite dies nicht tut?

Luc Touraille
quelle

Antworten:

86

result_ofwurde in Boost eingeführt und dann in TR1 und schließlich in C ++ 0x enthalten. Hat result_ofdaher einen Vorteil, der abwärtskompatibel ist (mit einer geeigneten Bibliothek).

decltype ist eine völlig neue Sache in C ++ 0x, beschränkt sich nicht nur auf den Rückgabetyp einer Funktion und ist eine Sprachfunktion.


Wie auch immer, auf gcc 4.5 result_ofist implementiert in Bezug auf 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
quelle
4
Soweit ich decltypeweiß, ist es hässlicher, aber auch mächtiger. result_ofkann nur für aufrufbare Typen verwendet werden und erfordert Typen als Argumente. Zum Beispiel können Sie result_ofhier nicht verwenden : template <typename T, typename U> auto sum( T t, U u ) -> decltype( t + u );Wenn die Argumente arithmetische Typen sein können (es gibt keine Funktion F, die Sie definieren können, um F(T,U)sie darzustellen t+u. Für benutzerdefinierte Typen könnten Sie. Auf die gleiche Weise (ich habe nicht wirklich damit gespielt) stelle ich mir vor Das Aufrufen von Mitgliedsmethoden könnte schwierig sein, result_ofohne Bindemittel oder Lambdas zu verwenden
David Rodríguez - Dribeas
2
Ein Hinweis: decltype benötigt Argumente, um die Funktion mit AFAIK aufzurufen. Ohne result_of <> ist es daher umständlich, den Typ von einer Vorlage zurückzugeben, ohne sich auf die Argumente mit gültigen Standardkonstruktoren zu verlassen.
Robert Mason
3
@RobertMason: Diese Argumente können mit std::declvaldem oben gezeigten Code abgerufen werden . Natürlich ist das hässlich :)
Kennytm
1
@ DavidRodríguez-dribeas Ihr Kommentar hat nicht geschlossene Klammern, die mit "(es gibt" :(
Navin
1
Noch ein Hinweis: result_ofund sein Hilfstyp result_of_tsind ab C ++ 17 zugunsten von invoke_resultund veraltet invoke_result_t, wodurch einige Einschränkungen des ersteren gemildert werden. Diese sind unten in en.cppreference.com/w/cpp/types/result_of aufgeführt .
Sigma
11

Wenn Sie den Typ von etwas benötigen, das nicht wie ein Funktionsaufruf ist, std::result_oftrifft dies einfach nicht zu. decltype()kann Ihnen den Typ eines beliebigen Ausdrucks geben.

Wenn wir uns nur auf die verschiedenen Methoden zur Bestimmung des Rückgabetyps eines Funktionsaufrufs (zwischen std::result_of_t<F(Args...)>und decltype(std::declval<F>()(std::declval<Args>()...)) beschränken, gibt es einen Unterschied.

std::result_of<F(Args...) ist definiert als:

Wenn der Ausdruck INVOKE (declval<Fn>(), declval<ArgTypes>()...)gut formuliert ist, wenn er als nicht bewerteter Operand behandelt wird (Abschnitt 5), benennt der Typ typedef des Mitglieds den Typ, decltype(INVOKE (declval<Fn>(), declval<ArgTypes>()...)); andernfalls gibt es keinen Elementtyp .

Der Unterschied zwischen result_of<F(Args..)>::typeund decltype(std::declval<F>()(std::declval<Args>()...)ist alles darüber INVOKE. Die Verwendung von declval/ decltypedirekt ist nicht nur etwas länger zu tippen, sondern auch nur gültig, wenn sie Fdirekt aufrufbar ist (ein Funktionsobjekttyp oder eine Funktion oder ein Funktionszeiger). result_ofunterstützt zusätzlich Zeiger auf Mitgliederfunktionen und Zeiger auf Mitgliedsdaten.

Anfänglich wird ein SFINAE-freundlicher Ausdruck verwendet declval/ decltypegarantiert, während std::result_ofdies zu einem schweren Fehler anstelle eines Abzugsfehlers führen kann. std::result_ofDies wurde in C ++ 14 korrigiert: Es muss jetzt SFINAE-freundlich sein (dank dieses Dokuments ).

Also auf einem konformen C ++ 14-Compiler std::result_of_t<F(Args...)>ist es absolut überlegen. Es ist klarer, kürzer und unterstützt mehr Fs .


† Es sei denn, Sie verwenden es in einem Kontext, in dem Sie keine Zeiger auf Mitglieder zulassen möchten. Dies std::result_of_twäre also in einem Fall erfolgreich, in dem Sie möglicherweise möchten, dass es fehlschlägt.

Mit Ausnahmen. Es unterstützt zwar Zeiger auf Mitglieder, result_offunktioniert jedoch nicht, wenn Sie versuchen, eine ungültige Typ-ID zu instanziieren . Dazu gehört eine Funktion, die eine Funktion zurückgibt oder abstrakte Typen nach Wert nimmt. Ex.:

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

int answer() { return 42; }

call(answer); // nope

Die richtige Verwendung wäre gewesen result_of_t<F&()>, aber das ist ein Detail, an das Sie sich nicht erinnern müssen decltype.

Barry
quelle
Ist die Verwendung für einen Nichtreferenztyp Tund eine Funktion korrekt? 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
Wenn das Argument, an das wir übergeben, a fist const T, sollten wir es auch verwenden result_of_t<F&&(const T&)>?
Zizheng Tai