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?