Anda harus memahami masalah penerusan. Anda dapat membaca seluruh masalah secara detail , tetapi saya akan meringkasnya.
Pada dasarnya, mengingat ungkapan itu E(a, b, ... , c)
, kami ingin f(a, b, ... , c)
persamaan itu setara. Di C ++ 03, ini tidak mungkin. Ada banyak upaya, tetapi semuanya gagal dalam beberapa hal.
Yang paling sederhana adalah menggunakan referensi-nilai:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
E(a, b, c);
}
Tetapi ini gagal menangani nilai sementara:, f(1, 2, 3);
karena itu tidak dapat terikat pada referensi-nilai.
Upaya selanjutnya mungkin:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(a, b, c);
}
Yang memperbaiki masalah di atas, tetapi membalik jepit. Sekarang gagal untuk memungkinkan E
untuk memiliki argumen non-const:
int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these
Usaha ketiga menerima-referensi const, tapi kemudian const_cast
's const
away:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}
Ini menerima semua nilai, bisa meneruskan semua nilai, tetapi berpotensi mengarah pada perilaku yang tidak terdefinisi:
const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!
Solusi akhir menangani semuanya dengan benar ... dengan biaya yang mustahil untuk dipertahankan. Anda memberikan kelebihan f
, dengan semua kombinasi const dan non-const:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);
N argumen membutuhkan 2 kombinasi N , mimpi buruk. Kami ingin melakukan ini secara otomatis.
(Ini secara efektif apa yang kita dapatkan dari kompiler untuk kita di C ++ 11.)
Di C ++ 11, kami mendapat kesempatan untuk memperbaikinya. Satu solusi memodifikasi aturan pengurangan template pada tipe yang ada, tetapi ini berpotensi memecah banyak kode. Jadi kita harus mencari cara lain.
Solusinya adalah dengan menggunakan referensi- nilai yang baru ditambahkan ; kita dapat memperkenalkan aturan baru ketika menyimpulkan tipe referensi nilai dan membuat hasil yang diinginkan. Lagipula, kita tidak mungkin memecahkan kode sekarang.
Jika diberi referensi ke referensi (catatan referensi adalah istilah yang mencakup arti keduanya T&
dan T&&
), kami menggunakan aturan berikut untuk mencari tahu tipe yang dihasilkan:
"[diberikan] tipe TR yang merupakan referensi ke tipe T, upaya untuk membuat tipe" referensi nilai ke cv TR "menciptakan tipe" referensi nilai ke T ", sementara upaya untuk membuat tipe" referensi nilai ke cv TR ”menciptakan tipe TR."
Atau dalam bentuk tabel:
TR R
T& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T& && -> T& // rvalue reference to cv TR -> TR (lvalue reference to T)
T&& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T&& && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
Selanjutnya, dengan pengurangan argumen templat: jika argumen adalah nilai l A, kami menyediakan argumen templat dengan referensi nilai lv untuk A. Jika tidak, kami menyimpulkan secara normal. Ini memberikan apa yang disebut referensi universal (istilah penerusan referensi sekarang menjadi yang resmi).
Mengapa ini berguna? Karena gabungan, kami mempertahankan kemampuan untuk melacak kategori nilai dari suatu jenis: jika itu adalah nilai, kami memiliki parameter referensi-nilai, jika tidak, kami memiliki parameter referensi-nilai.
Dalam kode:
template <typename T>
void deduce(T&& x);
int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)
Hal terakhir adalah "meneruskan" kategori nilai variabel. Perlu diingat, begitu berada di dalam fungsi, parameter dapat diteruskan sebagai nilai untuk apa pun:
void foo(int&);
template <typename T>
void deduce(T&& x)
{
foo(x); // fine, foo can refer to x
}
deduce(1); // okay, foo operates on x which has a value of 1
Itu tidak baik. E perlu mendapatkan kategori nilai yang sama dengan yang kami dapatkan! Solusinya adalah ini:
static_cast<T&&>(x);
Apa fungsinya? Anggap kita berada di dalam deduce
fungsi, dan kita telah melewati suatu nilai. Ini berarti T
a A&
, dan tipe target untuk pemeran statis adalah A& &&
, atau adil A&
. Karena x
sudah menjadi A&
, kami tidak melakukan apa-apa dan dibiarkan dengan referensi nilai tinggi.
Ketika kita telah melewati nilai, T
adalah A
, jadi tipe target untuk pemeran statis adalah A&&
. Para pemain menghasilkan ekspresi nilai, yang tidak lagi dapat diteruskan ke referensi nilai . Kami telah mempertahankan kategori nilai parameter.
Menyatukan ini memberi kita "penerusan sempurna":
template <typename A>
void f(A&& a)
{
E(static_cast<A&&>(a));
}
Ketika f
menerima nilai, E
mendapat nilai. Saat f
menerima nilai, E
dapatkan nilai. Sempurna.
Dan tentu saja, kami ingin menyingkirkan yang jelek. static_cast<T&&>
samar dan aneh untuk diingat; mari kita buat fungsi utilitas yang disebut forward
, yang melakukan hal yang sama:
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
f
fungsi, dan bukan ekspresi?const int i
akan diterima:A
disimpulkanconst int
. Kegagalan adalah untuk nilai-nilai literal. Perhatikan juga bahwa untuk panggilan kededuced(1)
, x adalahint&&
, tidakint
(penerusan yang sempurna tidak pernah membuat salinan, seperti yang akan dilakukan jikax
akan menjadi parameter dengan nilai). HanyaT
ituint
. Alasan yangx
mengevaluasi nilai lv dalam forwarder adalah karena rujukan yang dinamai rvalue menjadi ekspresi lvalue.forward
atau dimove
sini? Atau hanya perbedaan semantik?std::move
harus dipanggil tanpa argumen templat eksplisit dan selalu menghasilkan nilai, sementarastd::forward
mungkin berakhir juga. Gunakanstd::move
saat Anda tahu Anda tidak lagi membutuhkan nilai dan ingin memindahkannya ke tempat lain, gunakanstd::forward
untuk melakukannya sesuai dengan nilai yang diteruskan ke templat fungsi Anda.Saya pikir memiliki kode konseptual yang mengimplementasikan std :: forward dapat menambah diskusi. Ini adalah slide dari Scott Meyers talk An Effective C ++ 11/14 Sampler
Fungsi
move
dalam kode tersebut adalahstd::move
. Ada implementasi (berfungsi) untuk itu sebelumnya dalam pembicaraan itu. Saya menemukan implementasi aktual std :: forward di libstdc ++ , di file move.h, tetapi sama sekali tidak instruktif.Dari sudut pandang pengguna, artinya
std::forward
adalah pemeran bersyarat untuk nilai. Ini bisa berguna jika saya menulis fungsi yang mengharapkan nilai atau nilai dalam parameter dan ingin meneruskannya ke fungsi lain sebagai nilai hanya jika diteruskan sebagai nilai. Jika saya tidak membungkus parameter dalam std :: forward, itu akan selalu diberikan sebagai referensi normal.Cukup yakin, itu mencetak
Kode didasarkan pada contoh dari pembicaraan yang disebutkan sebelumnya. Geser 10, sekitar pukul 15:00 dari awal.
sumber
Jika Anda menggunakan referensi nilai yang dinamai dalam ekspresi itu sebenarnya adalah nilai (karena Anda merujuk ke objek dengan nama). Perhatikan contoh berikut:
Sekarang, jika kita memanggil
outer
seperti inikami ingin 17 dan 29 diteruskan ke # 2 karena 17 dan 29 adalah literal bilangan bulat dan dengan demikian nilainya. Tetapi karena
t1
dant2
dalam ekspresiinner(t1,t2);
adalah nilai, Anda akan memanggil # 1 alih-alih # 2. Itu sebabnya kita perlu mengubah referensi kembali menjadi referensi tanpa namastd::forward
. Jadi,t1
inouter
selalu merupakan ekspresi lvalue sementaraforward<T1>(t1)
mungkin merupakan ekspresi rvalue tergantung padaT1
. Yang terakhir hanya ekspresi lvalue jikaT1
merupakan referensi lvalue. DanT1
hanya dideduksi menjadi referensi nilai jika argumen pertama ke luar adalah ekspresi nilai.sumber
Jika, setelah membuat instance,
T1
adalah tipechar
, danT2
kelas, Anda ingin lulust1
per salinan dant2
perconst
referensi. Nah, kecuali jikainner()
mengambilnya per non-const
referensi, yaitu, dalam hal ini Anda juga ingin melakukannya.Cobalah untuk menulis seperangkat
outer()
fungsi yang mengimplementasikan ini tanpa referensi nilai, menyimpulkan cara yang tepat untuk meneruskan argumen dariinner()
tipe. Saya pikir Anda akan membutuhkan sesuatu 2 ^ 2 dari mereka, cukup template-meta hal yang lumayan untuk menyimpulkan argumen, dan banyak waktu untuk mendapatkan ini dengan benar untuk semua kasus.Dan kemudian seseorang datang dengan
inner()
argumen yang mengambil argumen per pointer. Saya pikir sekarang menjadi 3 ^ 2. (Atau 4 ^ 2. Sial, aku tidak mau repot untuk berpikir apakahconst
pointer akan membuat perbedaan.)Dan kemudian bayangkan Anda ingin melakukan ini untuk lima parameter. Atau tujuh.
Sekarang Anda tahu mengapa beberapa pikiran cerdas muncul dengan "penerusan yang sempurna": Itu membuat kompiler melakukan semua ini untuk Anda.
sumber
Poin yang belum dibuat sejernih kristal adalah yang
static_cast<T&&>
menanganiconst T&
dengan benar juga.Program:
Menghasilkan:
Perhatikan bahwa 'f' harus menjadi fungsi templat. Jika hanya didefinisikan sebagai 'void f (int && a)' ini tidak berfungsi.
sumber
Mungkin bermanfaat untuk menekankan bahwa forward harus digunakan bersama dengan metode luar dengan forwarding / referensi universal. Menggunakan maju dengan sendirinya sebagai pernyataan berikut diizinkan, tetapi tidak ada gunanya selain menyebabkan kebingungan. Komite standar mungkin ingin menonaktifkan fleksibilitas seperti itu jika tidak, mengapa kita tidak menggunakan static_cast saja?
Menurut pendapat saya, bergerak dan maju adalah pola desain yang merupakan hasil alami setelah tipe referensi r-nilai diperkenalkan. Kita tidak boleh menyebutkan metode dengan asumsi itu digunakan dengan benar kecuali dilarang penggunaan yang salah.
sumber