Apa saja kegunaan dari decltype (otomatis)?

151

Dalam c ++ 14 decltype(auto)idiom tersebut diperkenalkan.

Biasanya penggunaannya adalah untuk memungkinkan autodeklarasi untuk menggunakan decltypeaturan pada ekspresi yang diberikan .

Mencari contoh penggunaan "baik" dari idiom saya hanya bisa memikirkan hal-hal seperti berikut (oleh Scott Meyers ), yaitu untuk pengurangan tipe pengembalian fungsi :

template<typename ContainerType, typename IndexType>                // C++14
decltype(auto) grab(ContainerType&& container, IndexType&& index)
{
  authenticateUser();
  return std::forward<ContainerType>(container)[std::forward<IndexType>(index)];
}

Apakah ada contoh lain di mana fitur bahasa baru ini berguna?

Nikos Athanasiou
sumber
2
posting ini pada dasarnya menyarankan untuk mencoba menghindari idiom ini karena ketika menggunakannya Anda memberikan lebih sedikit pilihan untuk optimasi ke kompiler Anda stackoverflow.com/a/20092875/2485710
user2485710
Saya pernah menggunakan decltype(auto)sesuatu yang mirip template<class U, V> decltype(auto) first(std::pair<U, V>& p) { return p.first; }, meskipun saya kemudian menyadari bahwa saya harus menggunakan return (p.first);yang ternyata berfungsi (tapi IIRC ini bahkan dimaksudkan).
dyp
@ user2485710 tidak yakin ini tentang pengoptimalan khusus, lebih banyak potensi kecelakaan jika decltype(auto)dapat menyebabkan sesuatu disalin / dipindahkan ke objek yang dinyatakan, berlawanan dengan harapan.
underscore_d

Jawaban:

170

Kembalikan jenis penerusan dalam kode generik

Untuk kode non-generik, seperti contoh awal yang Anda berikan, Anda dapat memilih secara manual untuk mendapatkan referensi sebagai tipe pengembalian:

auto const& Example(int const& i) 
{ 
    return i; 
}

tetapi dalam kode generik Anda ingin dapat meneruskan tipe pengembalian dengan sempurna tanpa mengetahui apakah Anda berurusan dengan referensi atau nilai. decltype(auto)memberi Anda kemampuan itu:

template<class Fun, class... Args>
decltype(auto) Example(Fun fun, Args&&... args) 
{ 
    return fun(std::forward<Args>(args)...); 
}

Menunda pengurangan jenis pengembalian dalam templat rekursif

Dalam Tanya Jawab ini beberapa hari yang lalu, rekursi tak terbatas selama instantiasi templat ditemukan ketika jenis pengembalian templat ditentukan sebagai decltype(iter(Int<i-1>{}))ganti decltype(auto).

template<int i> 
struct Int {};

constexpr auto iter(Int<0>) -> Int<0>;

template<int i>
constexpr auto iter(Int<i>) -> decltype(auto) 
{ return iter(Int<i-1>{}); }

int main() { decltype(iter(Int<10>{})) a; }

decltype(auto)digunakan di sini untuk menunda pengurangan tipe pengembalian setelah debu instantiation template telah menetap.

Penggunaan lainnya

Anda juga dapat menggunakan decltype(auto)dalam konteks lain, misalnya konsep Standar N3936 juga menyatakan

7.1.6.4 penentu otomatis [dcl.spec.auto]

1 autodan decltype(auto)penspesifikasi tipe menunjuk tipe placeholder yang akan diganti nanti, baik dengan deduksi dari inisialisasi atau dengan spesi fi kasi eksplisit dengan tipe trailing-return-type. The autotipe-spesifik er juga digunakan untuk menandakan bahwa lambda adalah lambda generik.

2 Tipe placeholder dapat muncul dengan deklarator fungsi di menyatakan-specifier-seq, tipe-specifier-seq, konversi-fungsi-id, atau tipe trailing-return, dalam konteks apa pun di mana deklarator tersebut valid . Jika deklarator fungsi menyertakan tipe trailing-return-tipe (8.3.5), yang menentukan tipe pengembalian yang dinyatakan dari fungsi. Jika tipe pengembalian yang dideklarasikan dari fungsi berisi tipe placeholder, tipe kembali dari fungsi tersebut disimpulkan dari pernyataan pengembalian dalam tubuh fungsi, jika ada.

Draf ini juga berisi contoh inisialisasi variabel ini:

int i;
int&& f();
auto x3a = i;                  // decltype(x3a) is int
decltype(auto) x3d = i;        // decltype(x3d) is int
auto x4a = (i);                // decltype(x4a) is int
decltype(auto) x4d = (i);      // decltype(x4d) is int&
auto x5a = f();                // decltype(x5a) is int
decltype(auto) x5d = f();      // decltype(x5d) is int&&
auto x6a = { 1, 2 };           // decltype(x6a) is std::initializer_list<int>
decltype(auto) x6d = { 1, 2 }; // error, { 1, 2 } is not an expression
auto *x7a = &i;                // decltype(x7a) is int*
decltype(auto)*x7d = &i;       // error, declared type is not plain decltype(auto)
TemplateRex
sumber
17
Apakah perbedaan perilaku (i)vs ihal baru di C ++ 14?
Danvil
14
@ Vanvil decltype(expr)dan decltype((expr))sudah berbeda dalam C ++ 11, ini menggeneralisasi perilaku itu.
TemplateRex
13
Saya baru saja belajar ini, terasa seperti keputusan desain yang mengerikan ... menambahkan nuansa tepat waktu ke makna sintaks kurung.
Kahler
Contoh yang selalu membuat jijik ini adalah sintaks file-ke-string satu-liner (juga disebutkan dalam tautan itu). Setiap bagian dari itu tampak terbelakang. Anda mungkin tidak mengharapkan ambiguitas sama sekali, dan menghapus kurung yang berlebihan dari sampel secara kompulsif; Anda akan mengharapkan ambiguitas untuk diselesaikan dengan proses eliminasi sesuai dengan SFINAE, tetapi para calon selain deklarasi dihilangkan terlebih dahulu (SF adalah AE); dan dengan frustrasi Anda mungkin melanjutkan begitu mengkompilasi berpikir orangtua yang sewenang-wenang memecahkan ambiguitas, tetapi mereka memperkenalkannya . Kebanyakan menjengkelkan bagi para profesor CS101 yang saya bayangkan.
John P
@TemplateRex: Tentang menunda resolusi tipe kembali dalam pertanyaan yang direferensikan: Sejauh yang saya lihat, dalam skenario tertentu , sederhana autoakan melakukan pekerjaan dengan baik, karena hasilnya tetap dikembalikan dengan nilai ... Atau apakah saya melewatkan sesuatu?
Aconcagua
36

Mengutip hal-hal dari sini :

  • decltype(auto)ini sangat berguna untuk menyimpulkan tipe kembalinya fungsi penerusan dan pembungkus yang serupa , di mana Anda ingin tipe tersebut “melacak” beberapa ekspresi yang Anda panggil.

  • Misalnya, mengingat fungsi di bawah ini:


   string  lookup1();
   string& lookup2();

  • Dalam C ++ 11 kita bisa menulis fungsi pembungkus berikut yang ingat untuk mempertahankan referensi-jenis yang kembali:

   string  look_up_a_string_1() { return lookup1(); }
   string& look_up_a_string_2() { return lookup2(); }

  • Di C ++ 14, kita dapat mengotomatisasi itu:

   decltype(auto) look_up_a_string_1() { return lookup1(); }
   decltype(auto) look_up_a_string_2() { return lookup2(); }

  • Namun, decltype(auto)tidak dimaksudkan untuk menjadi fitur yang banyak digunakan di luar itu.

  • Secara khusus, meskipun dapat digunakan untuk mendeklarasikan variabel lokal , melakukan hal itu mungkin hanya antipattern karena referensi variabel lokal seharusnya tidak bergantung pada ekspresi inisialisasi.

  • Juga, ini sensitif terhadap bagaimana Anda menulis pernyataan pengembalian.

  • Misalnya, dua fungsi di bawah ini memiliki tipe pengembalian yang berbeda:


   decltype(auto) look_up_a_string_1() { auto str = lookup1(); return str; }
   decltype(auto) look_up_a_string_2() { auto str = lookup2(); return(str); }

  • Pengembalian pertama string, pengembalian kedua string&, yang merupakan referensi ke variabel lokal str.

Dari proposal Anda dapat melihat lebih banyak kegunaan yang dimaksudkan.

101010
sumber
3
Kenapa tidak gunakan saja autountuk pengembalian?
BЈовић
@ BЈовић dapat bekerja dengan pengurangan tipe pengembalian umum (yaitu, autokembali) juga tetapi OP meminta penggunaan khusus decltype(auto).
101010
3
Pertanyaannya masih relevan. Seperti apa tipe pengembaliannya auto lookup_a_string() { ... } ? Apakah selalu tipe non-referensi? Dan karena auto lookup_a_string() ->decltype(auto) { ... }itu diperlukan untuk memaksa untuk memungkinkan referensi (dalam beberapa kasus) dikembalikan?
Aaron McDaid
@AaronMcDaid Deductible autodidefinisikan sebagai templat nilai by pass, jadi ya tidak bisa menjadi referensi. Harap tunggu autobisa apa saja termasuk referensi, tentu saja.
curiousguy
4
Contoh tambahan yang layak disebutkan adalah mengembalikan elemen a std::vector. Katakan sudah template<typename T> struct S { auto & operator[](std::size_t i) { return v[i]; } std::vector<T> v; }. Kemudian S<bool>::operator[]akan mengembalikan referensi yang menggantung karena spesialisasi std::vector<bool>. Mengubah tipe pengembalian untuk decltype(auto)menghindari masalah ini.
Xoph