( Ayrıca C ++ 11 yanıtım için buraya bakın )
Bir C ++ programını ayrıştırmak için, derleyicinin belirli isimlerin tür olup olmadığını bilmesi gerekir. Aşağıdaki örnek bunu göstermektedir:
t * f;
Bu nasıl ayrıştırılmalıdır? Birçok dil için, bir derleyicinin bir kod satırının ne yaptığını ayrıştırmak ve temel olarak bilmek için bir adın anlamını bilmesi gerekmez. Bununla birlikte, C ++ 'da, yukarıdaki t
yöntem , ne anlama geldiğine bağlı olarak çok farklı yorumlar verebilir . Bir türse, bir işaretçi bildirimi olacaktır f
. Ancak bir tür değilse, çarpma olacaktır. C ++ Standardı paragraf (3/7) 'de der ki:
Bazı adlar türleri veya şablonları belirtir. Genel olarak, bir adla karşılaşıldığında, adın içerdiği programı ayrıştırmaya devam etmeden önce bu adın bu varlıklardan birini gösterip göstermediğini belirlemek gerekir. Bunu belirleyen işleme isim arama denir.
Derleyici t::x
, t
bir şablon türü parametresine başvuruyorsa , adın ne anlama geldiğini nasıl öğrenir ? x
bir çoğaltılabilecek bir statik int veri elemanı olabilir veya bir bildirime yol açabilecek iç içe bir sınıf veya typedef de olabilir. Bir ad bu özelliğe sahipse - gerçek şablon argümanları bilinene kadar aranamazsa - buna bağımlı bir ad denir (şablon parametrelerine "bağlıdır").
Kullanıcı şablonu başlatana kadar beklemenizi öneririz:
Kullanıcı şablonu başlatana kadar bekleyelim ve daha sonra gerçek anlamını öğrenelim t::x * f;
.
Bu işe yarayacak ve aslında Standart tarafından olası bir uygulama yaklaşımı olarak izin verilecektir. Bu derleyiciler temel olarak şablonun metnini dahili bir ara belleğe kopyalar ve yalnızca bir örnekleme gerektiğinde şablonu ayrıştırır ve muhtemelen tanımdaki hataları algılar. Ancak, şablonun kullanıcılarını (yoksul meslektaşları!) Bir şablonun yazarı tarafından yapılan hatalarla rahatsız etmek yerine, diğer uygulamalar, bir örnekleme bile gerçekleşmeden önce şablonları en kısa zamanda kontrol etmeyi ve tanımdaki hataları vermeyi seçer.
Yani derleyiciye belirli isimlerin tür olduğunu ve belirli isimlerin olmadığını söylemenin bir yolu olmalı.
"Typename" anahtar kelimesi
Cevap: Derleyicinin bunu nasıl ayrıştırması gerektiğine karar veriyoruz . Eğer t::x
bir bağımlı adıdır, o zaman biz bunu öneki gerekebilir typename
belirli bir şekilde bunu ayrıştırmak için derleyici anlatmak için. Standart (14.6 / 2) 'de diyor:
Bir şablon bildiriminde veya tanımında kullanılan ve bir şablon parametresine bağlı olan bir adın, uygulanabilir ad araması bir tür adı bulamazsa veya ad, tür anahtar sözcüğü tarafından nitelendirilmedikçe, bir tür adlandırmayacağı varsayılır.
Birçok isim vardır typename
, gerekli değildir, çünkü bir konstrukt kendisini ayrıştırmak nasıl uygulanabilir adı şablon tanımında arama, figür ile derleyici kutu, - ile örneğin T *f;
, ne zaman T
bir tür şablon parametresidir. Ancak t::x * f;
bir bildirge olabilmek için şöyle yazılmalıdır typename t::x *f;
. Anahtar kelimeyi atlarsanız ve ad türsüz olarak alınırsa, ancak örnekleme bir tür olduğunu gösterirse, derleyici tarafından olağan hata iletileri verilir. Bazen hata sonuç olarak tanımlanır:
// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;
Sözdizimi typename
yalnızca nitelikli adlardan önce izin verir - bu nedenle, nitelendirilmemiş adların her zaman türlere atıfta bulunduğu anlamına geldiği kabul edilir.
Tanıtım metninde belirtildiği gibi, şablonları belirten adlar için benzer bir yakalama vardır.
"Şablon" anahtar kelimesi
Yukarıdaki ilk alıntıyı ve Standardın şablonlar için nasıl özel işlem gerektirdiğini hatırlıyor musunuz? Aşağıdaki masum görünümlü örneği ele alalım:
boost::function< int() > f;
Bir insan okuyucu için açık görünebilir. Derleyici için öyle değil. Aşağıdaki keyfi tanımını düşünün boost::function
ve f
:
namespace boost { int function = 0; }
int main() {
int f = 0;
boost::function< int() > f;
}
Bu aslında geçerli bir ifade ! Bu karşılaştırma operatör daha-az kullanan boost::function
sıfır (karşı int()
) ve daha sonra elde edilen karşılaştırma büyüktür operatör kullanır bool
karşı f
. Ancak, bildiğiniz gibi boost::function
, gerçek hayatta bir şablon, bu yüzden derleyici bilir (14.2 / 3):
Ad araması (3.4) bir adın şablon adı olduğunu fark ettikten sonra, bu adın ardından <gelirse, <her zaman bir şablon-bağımsız değişken listesinin başlangıcı olarak alınır ve hiçbir zaman bir ad olarak kullanılmaz. operatör.
Şimdi ile aynı soruna geri döndük typename
. Kodu ayrıştırırken adın şablon olup olmadığını henüz bilmiyorsak ne olur? template
Tarafından belirtildiği gibi şablon adından hemen önce eklememiz gerekecek 14.2/4
. Bu şuna benzer:
t::template f<int>(); // call a function template
Şablon adları yalnızca a'dan sonra ::
değil, bir ->
veya .
bir sınıf üyesi erişiminden sonra da oluşabilir . Anahtar kelimeyi oraya da eklemeniz gerekir:
this->template f<int>(); // call a function template
Bağımlılıklar
Raflarında kalın Standardese kitapları olan ve tam olarak neden bahsettiğimi bilmek isteyen insanlar için, bunun Standartta nasıl belirtildiği hakkında biraz konuşacağım.
Şablon bildirimlerinde, şablonun örneğini oluşturmak için hangi şablon bağımsız değişkenlerini kullandığınıza bağlı olarak bazı yapıların farklı anlamları vardır: İfadeler farklı türlere veya değerlere sahip olabilir, değişkenler farklı türlere sahip olabilir veya işlev çağrıları farklı işlevleri çağırabilir. Bu tür yapıların genellikle şablon parametrelerine bağlı olduğu söylenir .
Standart kuralları bir yapının bağımlı olup olmadığına göre kesin olarak tanımlar. Onları mantıksal olarak farklı gruplara ayırır: Biri türleri yakalar, diğeri ifadeleri yakalar. İfadeler, değerlerine ve / veya türlerine bağlı olabilir. Bu yüzden, tipik örnekler ekledik:
- Bağımlı türler (örneğin: bir tür şablonu parametresi
T
)
- Değere bağlı ifadeler (örneğin: tür olmayan bir şablon parametresi
N
)
- Türe bağlı ifadeler (örneğin: tür şablon parametresine yayınlama
(T)0
)
Kuralları çoğu sezgiseldir ve yinelemeli inşa edilir: Örneğin, şekilde inşa edilen bir tipi T[N]
ise, bir bağımlı tür N
bir değer bağımlı ifadesidir ya T
bağımlı bir türüdür. Bunun ayrıntıları bölüm (14.6.2/1
) bağımlı türler, (14.6.2.2)
türe bağlı ifadeler ve (14.6.2.3)
değere bağlı ifadeler için okunabilir .
Bağımlı isimler
Standart, tam olarak bağımlı bir adın ne olduğu konusunda biraz belirsizdir . Basit bir okumada (bilirsiniz, en az sürpriz ilkesi), bağımlı bir isim olarak tanımladığı tek şey aşağıdaki işlev adları için özel durumdur. Ancak T::x
, somutlaştırma bağlamında da açıkça görülmesi gerektiğinden, aynı zamanda bağımlı bir isim de olmalıdır (neyse ki, C ++ 14'ün ortalarından beri komite bu kafa karıştırıcı tanımın nasıl düzeltileceğini araştırmaya başlamıştır).
Bu sorunu önlemek için, Standart metnin basit bir yorumuna başvurdum. Bağımlı türleri veya ifadeleri ifade eden tüm yapılardan, bunların bir alt kümesi adları temsil eder. Dolayısıyla bu isimler "bağımlı isimler" dir. Bir isim farklı şekillerde olabilir - Standart diyor ki:
Bir ad, bir varlığı veya etiketi (6.6.4,) tanımlayan bir tanımlayıcı (2.11), işleç işlev kimliği (13.5), dönüştürme işlevi işlev kimliği (12.3.2) veya şablon kimliğinin (14.2) kullanılmasıdır. 6.1)
Bir tanımlayıcı yalnızca düz bir karakter / rakam dizisidir, sonraki iki karakter ise operator +
ve operator type
formdur. Son biçim template-name <argument list>
. Tüm bunlar adlardır ve Standartta geleneksel kullanımla, bir ad ayrıca bir adın hangi ad boşluğuna veya sınıfa bakılması gerektiğini söyleyen niteleyiciler içerebilir.
Değere bağlı bir ifade 1 + N
bir ad değil, bir ifadedir N
. Ad olan tüm bağımlı yapıların alt kümesine bağımlı ad denir . Ancak işlev adları, bir şablonun farklı örneklemelerinde farklı anlamlara sahip olabilir, ancak maalesef bu genel kural tarafından yakalanmaz.
Bağımlı işlev adları
Öncelikle bu makalenin bir endişesi değil, ancak bahsetmeye değer: İşlev adları ayrı ayrı ele alınan bir istisnadır. Bir tanımlayıcı işlev adı tek başına değil, çağrıda kullanılan türe bağlı bağımsız değişken ifadelerine bağlıdır. Örnekte f((T)0)
, f
bağımlı bir addır. Standartta bu, adresinde belirtilir (14.6.2/1)
.
Ek notlar ve örnekler
Yeterli durumlarda hem typename
ve 'ye ihtiyacımız var template
. Kodunuz aşağıdaki gibi görünmelidir
template <typename T, typename Tail>
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
typedef typename Tail::template inUnion<U> dummy;
};
// ...
};
Anahtar kelimenin template
her zaman bir adın son bölümünde görünmesi gerekmez. Aşağıdaki örnekte olduğu gibi, kapsam olarak kullanılan bir sınıf adının önünde ortada görünebilir
typename t::template iterator<int>::value_type v;
Bazı durumlarda, aşağıda ayrıntılı olarak açıklandığı gibi anahtar kelimeler yasaktır
Bağımlı bir temel sınıfın adına yazmanıza izin verilmez typename
. Verilen adın bir sınıf türü adı olduğu varsayılmaktadır. Bu, hem temel sınıf listesindeki hem de yapıcı başlatıcı listesindeki adlar için geçerlidir:
template <typename T>
struct derive_from_Has_type : /* typename */ SomeBase<T>::type
{ };
Kullanım bildirimlerinde sonuncudan template
sonra kullanmak mümkün değildir ::
ve C ++ komitesi bir çözüm üzerinde çalışmamalarını söyledi .
template <typename T>
struct derive_from_Has_type : SomeBase<T> {
using SomeBase<T>::template type; // error
using typename SomeBase<T>::type; // typename *is* allowed
};