Yönlendirme sorununu anlamalısınız. Şunları yapabilirsiniz tamamını ayrıntılı bir sorunu okumak , ama özetlemek gerekir.
Temel olarak, ifade verildiğinde, ifadenin eşdeğer olmasını E(a, b, ... , c)
istiyoruz f(a, b, ... , c)
. C ++ 03'te bu mümkün değildir. Birçok deneme var, ama hepsi bir bakıma başarısız oluyor.
En basit olanı bir değer referansı kullanmaktır:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
E(a, b, c);
}
Ancak bu geçici değerleri işleyemez: f(1, 2, 3);
çünkü bunlar bir değer referansına bağlanamaz.
Bir sonraki girişim şunlar olabilir:
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(a, b, c);
}
Bu yukarıdaki sorunu giderir, ancak flopları çevirir. Artık E
const olmayan bağımsız değişkenlere izin verilmiyor:
int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these
Üçüncü girişim const-referansları kabul eder ama sonra const_cast
's const
uzak:
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));
}
Bu, tüm değerleri kabul eder, tüm değerleri aktarabilir, ancak potansiyel olarak tanımlanmamış davranışa yol açar:
const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!
Nihai bir çözüm her şeyi doğru bir şekilde işler ... bakımı imkansız olmak pahasına. Sen aşırı yükleri sağlamak f
ile, tüm const olmayan const kombinasyonları:
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 argümanları 2 N kombinasyonu, bir kabus gerektirir . Bunu otomatik olarak yapmak istiyoruz.
(Derleyicinin C ++ 11'de bizim için yapmasını sağladığımız şey budur.)
C ++ 11'de bunu düzeltme şansımız var. Bir çözüm, mevcut türlerde şablon kesinti kurallarını değiştirir, ancak bu potansiyel olarak büyük miktarda kodu ihlal eder. Bu yüzden başka bir yol bulmalıyız.
Çözüm bunun yerine yeni eklenen rvalue referanslarını kullanmaktır ; değer referansı türlerini çıkarırken yeni kurallar getirebilir ve istenen sonuçları yaratabiliriz. Sonuçta, şimdi kodu kıramayacağız.
Bir referansa referans verildiyse (not referansı hem T&
ve hem de anlamını kapsayan bir terimdir T&&
), ortaya çıkan türü bulmak için aşağıdaki kuralı kullanırız:
"[verilen] T türüne referans olan bir TR türü," cv TR'ye lvalue referansı türünü yaratma girişimi "T'ye lvalue referansı türünü yaratırken," rvalue referansı türünü yaratma girişimi " cv TR ”, TR türünü oluşturur."
Veya tablo şeklinde:
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)
Daha sonra, şablon argümanı kesinti ile: bir argüman bir A değeri ise, şablon bağımsız değişkenine A'ya bir değer referansı sağlarız. Aksi takdirde, normal olarak çıkarırız. Bu, sözde evrensel referanslar verir ( yönlendirme referansı terimi şimdi resmi olan).
Bu neden faydalı? Birleştirildiğinden, bir türün değer kategorisini takip etme yeteneğimizi koruduğumuzdan: eğer bir değerse, bir değer değeri referans parametresimiz var, aksi takdirde bir değer değeri referans parametresimiz var.
Kodda:
template <typename T>
void deduce(T&& x);
int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)
Son şey, değişkenin değer kategorisini "iletmektir". Unutmayın, fonksiyonun içine girdikten sonra parametre herhangi bir değere lvalue olarak geçirilebilir:
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
Bu iyi değil. E'nin sahip olduğumuz değer kategorisini alması gerekiyor! Çözüm şudur:
static_cast<T&&>(x);
Bu ne yapar? deduce
İşlevin içinde olduğumuzu düşünün ve bize bir değer verildi. Bu T
, a anlamına gelir A&
ve bu nedenle statik yayın için hedef türü A& &&
veya adil olur A&
. Yana x
zaten bir A&
hiçbir şey yapamaz ve bir lvalue referansla bırakılır.
Biz rvalue geçirilen oldum zaman T
olduğu A
statik döküm hedef türüdür yüzden A&&
. Döküm, artık bir değer referansına geçirilemeyen bir değerlik ifadesiyle sonuçlanır . Parametrenin değer kategorisini koruduk.
Bunları bir araya getirmek bize "mükemmel yönlendirme" sağlar:
template <typename A>
void f(A&& a)
{
E(static_cast<A&&>(a));
}
Bir f
lvalue aldığında, bir lvalue E
alır. Bir f
rvalue aldığında, bir rvalue E
alır. Mükemmel.
Ve elbette çirkinlerden kurtulmak istiyoruz. static_cast<T&&>
şifreli ve hatırlanması garip; bunun yerine forward
aynı şeyi yapan bir yardımcı fonksiyon yapalım :
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);
f
bir işlev değil, bir ifade olabilir mi?