Kısa cevap: x
Bağımlı bir ad oluşturmak için, şablon parametresi bilinene kadar arama ertelenir.
Uzun cevap: bir derleyici bir şablon gördüğünde, template parametresini görmeden hemen belirli kontroller yapması beklenir. Diğerleri parametre bilinene kadar ertelenir. Buna iki fazlı derleme denir ve MSVC bunu yapmaz, ancak standart tarafından gereklidir ve diğer büyük derleyiciler tarafından uygulanır. İsterseniz, derleyici şablonu görür görmez derlemelidir (bir tür iç ayrıştırma ağacı temsiline) ve derlemeyi daha sonraya kadar derlemeyi ertelemelidir.
Belirli örneklemelerinden ziyade şablonun kendisinde gerçekleştirilen denetimler, derleyicinin şablondaki kodun dilbilgisini çözebilmesini gerektirir.
C ++ (ve C) 'de kod dilbilgisini çözmek için bazen bir şeyin tür olup olmadığını bilmeniz gerekir. Örneğin:
#if WANT_POINTER
typedef int A;
#else
int A;
#endif
static const int x = 2;
template <typename T> void foo() { A *x = 0; }
A bir türse, bir işaretçi bildirir (global gölgeden başka bir etkisi yoktur x
). A bir nesne ise, bu çarpmadır (ve bir operatörün yasadışı aşırı yüklenmesine engel olur ve bir rvalue atar). Yanlışsa, bu hata faz 1'de teşhis edilmelidir , standart tarafından belirli bir örneklemede değil , şablonda bir hata olarak tanımlanır . Şablon hiçbir zaman somutlaştırılmasa bile, A bir ise int
, yukarıdaki kod yanlış biçimlendirilmiş ve teşhis edilmesi gerekir, tıpkı foo
bir şablon olmasa bile , basit bir işlev gibi.
Artık standart , şablon parametrelerine bağlı olmayan isimlerin 1. aşamada çözülebilir olması gerektiğini söylüyor. A
Burada bağımlı bir isim değil, türden bağımsız olarak aynı şeyi ifade ediyor T
. Bu nedenle, 1. aşamada bulunup kontrol edilebilmesi için şablon tanımlanmadan önce tanımlanması gerekir.
T::A
T'ye bağlı bir isim olurdu. 1. aşamada bunun bir tür olup olmadığını bilemeyiz. Sonunda T
bir örneklemede olduğu gibi kullanılacak olan tür henüz tam olarak tanımlanmamıştır ve öyle olsa bile, şablon parametrelerimiz olarak hangi türlerin kullanılacağını bilmiyoruz. Ancak, 1. Aşama, kötü biçimlendirilmiş şablonlar için kontroller yapmak için dilbilgisini çözmeliyiz. Bu yüzden standardın bağımlı isimler için bir kuralı vardır - derleyici , tür typename
olduklarını belirtmekle yetinmedikçe veya belirli belirsiz bağlamlarda kullanılmadıkça , tür olmadığını varsaymalıdır . Örneğin template <typename T> struct Foo : T::A {};
, T::A
bir temel sınıf olarak kullanılır ve bu nedenle açık bir şekilde bir türdür. Eğer Foo
bir veri elemanı vardır bazı tip örneğiA
iç içe tip A yerine, örneklemede (aşama 2) kodda bir hata var, şablonda (aşama 1) bir hata değil.
Peki ya bağımlı bir temel sınıfa sahip bir sınıf şablonu?
template <typename T>
struct Foo : Bar<T> {
Foo() { A *x = 0; }
};
A bağımlı bir isim mi, değil mi? Temel sınıflarda, temel sınıfta herhangi bir ad görünebilir. Bu yüzden A'nın bağımlı bir isim olduğunu söyleyebiliriz ve ona tür olmayan olarak davranabiliriz. Bu, Foo'daki her adın bağımlı olduğu istenmeyen bir etkiye sahip olacaktır ve bu nedenle Foo'da kullanılan her türün (yerleşik türler hariç) nitelendirilmesi gerekir. Foo'nun içinde şunları yazmanız gerekir:
typename std::string s = "hello, world";
çünkü std::string
bağımlı bir ad olur ve bu nedenle aksi belirtilmedikçe tür olmayan olarak kabul edilir. Ah!
Tercih ettiğiniz kodun ( return x;
) kullanılmasına izin verilmesinde ikinci bir sorun , daha Bar
önce tanımlanmış olsa Foo
ve x
bu tanımda üye olmasa bile , birisinin daha sonra bir tür Bar
için bir uzmanlık tanımlayabilmesidir Baz
, bu şekilde Bar<Baz>
bir veri üyesi olur x
ve Foo<Baz>
. Bu örneklemede, şablonunuz genel öğeyi döndürmek yerine veri üyesini döndürür x
. Ya da tersine, temel şablon tanımına sahip Bar
olsaydı x
, onsuz bir uzmanlık tanımlayabilirler ve şablonunuz x
geri döndürülecek bir küresel arardı Foo<Baz>
. Bence bunun, yaşadığınız sorun kadar şaşırtıcı ve üzücü olduğuna karar verildi, ama sessizce şaşırtıcı bir hata atmak yerine, şaşırtıcı.
Bu sorunlardan kaçınmak için yürürlükte olan standart, sınıf şablonlarının bağımlı temel sınıflarının açıkça talep edilmedikçe arama için dikkate alınmadığını söylüyor. Bu, her şeyin sadece bağımlı bir tabanda bulunabilmesi nedeniyle bağımlı olmasını engeller. Ayrıca gördüğünüz istenmeyen bir etkiye sahiptir - temel sınıftan bir şeyleri nitelemeniz gerekir veya bulunmaz. A
Bağımlı kılmanın üç yaygın yolu vardır :
using Bar<T>::A;
sınıfta - A
şimdi buna Bar<T>
bağlı olan bir şeye atıfta bulunuyor .
Bar<T>::A *x = 0;
kullanım noktasında - Yine, A
kesinlikle içeride Bar<T>
. Bu, typename
kullanılmadığından beri çarpmadır , bu yüzden muhtemelen kötü bir örnek, ancak operator*(Bar<T>::A, x)
bir değer döndürüp döndürmediğini öğrenmek için örneklemeye kadar beklememiz gerekecek . Kim bilir, belki de ...
this->A;
kullanım noktasında - A
bir üyedir, bu yüzden eğer değilse Foo
, temel sınıfta olmalıdır, yine standart bunun bağımlı olduğunu söylüyor.
İki aşamalı derleme zor ve zordur ve kodunuzda ekstra ayrıntılar için bazı şaşırtıcı gereksinimler getirir. Fakat daha ziyade demokrasi gibi, muhtemelen diğerlerinden farklı olarak bir şeyler yapmanın en kötü yolu.
Örneğinizde , temel sınıfta iç içe bir tür return x;
olup olmadığını anlamsız bir şekilde iddia edebilirsiniz x
, bu nedenle dil (a) bunun bağımlı bir ad olduğunu ve (2) bunu tür olmayan olarak ele almasını ve kodunuz olmadan çalışır this->
. Bir dereceye kadar, çözümden sizin durumunuzda geçerli olmayan bir soruna verilen teminat hasarının kurbanı olursunuz, ancak hala temel sınıfınızın potansiyel olarak altında küreselleri gölgeleyen ya da düşündüğünüz isimlere sahip olmayan isimler sunma sorunu var onlar vardı ve onun yerine küresel bir varlık bulundu.
Ayrıca muhtemelen varsayılan bağımlı isimlerin (her nasılsa bir nesne olarak belirtilmediği sürece türünü varsayıyorum) veya varsayılan (daha fazla bağlam duyarlı olması gerektiğini bunun için tersi olması gerektiğini iddia olabilir std::string s = "";
, std::string
başka bir şey gramer yaptığı için bir tür olarak okunabilir anlam, std::string *s = 0;
belirsiz olsa da). Yine, kuralların nasıl kabul edildiğini tam olarak bilmiyorum. Benim tahminim, gerekli olabilecek metin sayfası sayısının, bağlamların bir tür aldığı ve hangi türlerin türünün olmadığı birçok özel kural oluşturmaya karşı hafifletilmesidir.