Perbedaan antara std :: result_of dan dectype

100

Saya mengalami kesulitan memahami kebutuhan std::result_ofdalam C ++ 0x. Jika saya mengerti dengan benar, result_ofdigunakan untuk mendapatkan jenis yang dihasilkan dari pemanggilan objek fungsi dengan jenis parameter tertentu. Sebagai contoh:

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

Saya tidak terlalu melihat perbedaannya dengan kode berikut:

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

atau

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

Satu-satunya masalah yang dapat saya lihat dengan dua solusi ini adalah kita perlu:

  • memiliki contoh dari functor untuk menggunakannya dalam ekspresi yang diteruskan ke jenis deklarasi.
  • tahu konstruktor yang ditentukan untuk functor.

Apakah saya benar dalam berpikir bahwa satu-satunya perbedaan antara decltypedan result_ofapakah yang pertama membutuhkan ekspresi sedangkan yang kedua tidak?

Luc Touraille
sumber

Jawaban:

86

result_ofitu diperkenalkan dalam Meningkatkan , dan kemudian dimasukkan dalam TR1 , dan akhirnya di C ++ 0x. Oleh karena itu result_ofmemiliki keuntungan yang kompatibel ke belakang (dengan perpustakaan yang sesuai).

decltype adalah hal yang sama sekali baru di C ++ 0x, tidak membatasi hanya untuk mengembalikan jenis fungsi, dan merupakan fitur bahasa.


Bagaimanapun, di gcc 4.5, result_ofdiimplementasikan dalam hal 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
sumber
4
Sejauh yang saya mengerti decltypelebih buruk tetapi juga lebih kuat. result_ofhanya dapat digunakan untuk tipe yang dapat dipanggil dan membutuhkan tipe sebagai argumen. Misalnya, Anda tidak dapat menggunakan di result_ofsini: template <typename T, typename U> auto sum( T t, U u ) -> decltype( t + u );jika argumen dapat berupa tipe aritmatika (tidak ada fungsi Fyang dapat Anda definisikan F(T,U)untuk mewakili t+u. Untuk tipe yang ditentukan pengguna, Anda bisa. Dengan cara yang sama (saya belum benar-benar bermain dengannya) saya bayangkan bahwa panggilan ke metode anggota mungkin sulit dilakukan result_oftanpa menggunakan pengikat atau lambda
David Rodríguez - dribeas
2
Satu catatan, dectype membutuhkan argumen untuk memanggil fungsi dengan AFAIK, jadi tanpa result_of <> akan canggung untuk mendapatkan tipe yang dikembalikan oleh template tanpa bergantung pada argumen yang memiliki konstruktor default yang valid.
Robert Mason
3
@RobertMason: Argumen tersebut dapat diambil menggunakan std::declval, seperti kode yang saya tunjukkan di atas. Tentu saja, ini jelek :)
kennytm
1
@ DavidRodríguez-dribeas Komentar Anda tidak ditutup dengan tanda kurung yang dibuka dengan "(ada" :(
Navin
1
Catatan lain: result_ofdan jenis helpernya result_of_ttidak digunakan lagi mulai C ++ 17 mendukung invoke_resultdan invoke_result_t, mengurangi beberapa pembatasan yang sebelumnya. Ini tercantum di bagian bawah en.cppreference.com/w/cpp/types/result_of .
sigma
11

Jika Anda membutuhkan jenis sesuatu yang bukan sesuatu seperti panggilan fungsi, std::result_ofitu tidak berlaku. decltype()dapat memberi Anda jenis ekspresi apa pun.

Jika kita membatasi diri hanya pada cara yang berbeda untuk menentukan jenis kembalian dari sebuah panggilan fungsi (antara std::result_of_t<F(Args...)>dan decltype(std::declval<F>()(std::declval<Args>()...)), maka ada perbedaan.

std::result_of<F(Args...) didefinisikan sebagai:

Jika ekspresi INVOKE (declval<Fn>(), declval<ArgTypes>()...)terbentuk dengan baik saat diperlakukan sebagai operan yang tidak dievaluasi (Klausul 5), tipe anggota typedef harus menamai tipe decltype(INVOKE (declval<Fn>(), declval<ArgTypes>()...)); jika tidak, tidak boleh ada tipe anggota.

Perbedaan antara result_of<F(Args..)>::typedan decltype(std::declval<F>()(std::declval<Args>()...)adalah tentang itu INVOKE. Menggunakan declval/ decltypesecara langsung, selain sedikit lebih lama untuk mengetik, hanya valid jika Fdapat dipanggil secara langsung (tipe objek fungsi atau fungsi atau penunjuk fungsi). result_ofSelain itu mendukung petunjuk ke fungsi anggota dan petunjuk ke data anggota.

Awalnya, menggunakan declval/ decltypemenjamin ekspresi ramah SFINAE, sedangkan std::result_ofbisa memberi Anda kesalahan keras alih-alih kegagalan deduksi. Itu telah diperbaiki di C ++ 14: std::result_ofsekarang harus ramah SFINAE (terima kasih untuk makalah ini ).

Jadi pada kompiler C ++ 14 yang sesuai, std::result_of_t<F(Args...)>sangat unggul. Ini jelas, lebih pendek, dan benar mendukung lebih Fs .


Kecuali, jika Anda menggunakannya dalam konteks di mana Anda tidak ingin mengizinkan petunjuk untuk anggota, jadi std::result_of_takan berhasil dalam kasus di mana Anda mungkin ingin gagal.

Dengan pengecualian. Meskipun mendukung pointer ke anggota, result_oftidak akan berfungsi jika Anda mencoba membuat id jenis yang tidak valid . Ini akan mencakup fungsi yang mengembalikan fungsi atau mengambil tipe abstrak berdasarkan nilai. Ex.:

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

int answer() { return 42; }

call(answer); // nope

Penggunaan yang benar akan dilakukan result_of_t<F&()>, tetapi itu adalah detail yang tidak perlu Anda ingat decltype.

Barry
sumber
Untuk tipe non-referensi Tdan fungsi template<class F> result_of_t<F&&(T&&)> call(F&& f, T&& arg) { return std::forward<F>(f)(std::move(arg)); }, apakah penggunaannya result_of_tbenar?
Zizheng Tai
Juga, jika argumen yang kita berikan fadalah a const T, haruskah kita gunakan result_of_t<F&&(const T&)>?
Zizheng Tai