"Şablon" ve "tür adı" anahtar kelimelerini nereye ve neden koymam gerekiyor?


1125

Şablonlar olarak, nerede ve neden katlanmak zorunda typenameve templatebağımlı adlarına?
Zaten bağımlı isimler tam olarak nedir?

Takip koduna sahibim:

template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        // Q: where to add typename/template here?
        typedef Tail::inUnion<U> dummy; 
    };
    template< > struct inUnion<T> {
    };
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
    // ...
    template<typename U> struct inUnion {
        char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
    };
    template< > struct inUnion<T> {
    };
};

Benim yaşadığım sorun sırada typedef Tail::inUnion<U> dummy. Bu inUnionbağımlı bir isim oldukça eminim ve VC ++ boğulma konusunda oldukça haklı.
Ayrıca templatederleyici inUnion bir şablon kimliği olduğunu söylemek için bir yere eklemek gerekir biliyorum . Ama tam olarak nerede? Ve sonra inUnion'un bir sınıf şablonu olduğunu varsayalım, yani inUnion<U>bir işlevi değil bir türü adlandırır mı?


1
Can sıkıcı bir soru: neden desteklenmiyor ::
Assaf Lavie

58
Politik hassasiyetler, taşınabilirlik.
MSalters

5
Gerçek sorunuzu ("nereye şablon / typename koymak için?") Son soruyu ve kodu başlangıçta koyarak daha iyi gözüktüm ve kodu 1024x ekrana sığacak şekilde yatay olarak kısalttım.
Johannes Schaub - litb

7
"Bağımlı isimler" ve "şablon" hakkında merak eden çoğu insanın "bağımlı isimler" in ne olduğunu bilmediği anlaşıldığı için "bağımlı isimler" başlıktan kaldırıldı. Bu şekilde onlar için daha az kafa karıştırıcı olmalı.
Johannes Schaub - litb

2
@MSalters: boost oldukça taşınabilir. Sadece siyasetin, desteğin genellikle kesilmesinin genel nedeni olduğunu söyleyebilirim. Bildiğim tek iyi neden artmış inşa süreleridir. Aksi takdirde, tekerleği yeniden icat etmek binlerce dolar kaybetmekle ilgilidir.
v.oddou

Yanıtlar:


1163

( 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 työ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, tbir şablon türü parametresine başvuruyorsa , adın ne anlama geldiğini nasıl öğrenir ? xbir ç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::xbir bağımlı adıdır, o zaman biz bunu öneki gerekebilir typenamebelirli 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 Tbir 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 typenameyalnı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::functionve 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::functionsıfır (karşı int()) ve daha sonra elde edilen karşılaştırma büyüktür operatör kullanır boolkarşı 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? templateTarafı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 Nbir değer bağımlı ifadesidir ya Tbağı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 typeformdur. 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 + Nbir 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), fbağımlı bir addır. Standartta bu, adresinde belirtilir (14.6.2/1).

Ek notlar ve örnekler

Yeterli durumlarda hem typenameve '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 templateher 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 templatesonra 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
     };

22
Bu yanıt, kaldırdığım önceki SSS girişimden kopyalandı, çünkü yeni "sahte sorular" oluşturmak yerine mevcut benzer soruları daha iyi kullanmalıyım. Son bölümün fikirlerini (typename / template'in yasak olduğu durumlar) düzenleyen @Prasoon'a teşekkürler .
Johannes Schaub - litb

1
Bu sözdizimini ne zaman kullanmalıyım? bu-> şablon f <int> (); Bu hata 'şablon' (bir disambiguator olarak) sadece şablonlar içinde izin verilir ama şablon anahtar kelime olmadan, iyi çalışıyor.
balki

1
Bugün benzer bir soru sordum, bu kısa süre sonra tekrarlandı: stackoverflow.com/questions/27923722/… . Yeni bir soru oluşturmak yerine bu soruyu yeniden canlandırmam istendi. Onları kopyalar olarak kabul etmediğimi söylemeliyim ama ben kimim, değil mi? Peki, typenamesözdizimi bu noktada tip isimlerinden başka alternatif yorumlara izin vermese bile uygulanacak bir sebep var mı?
JorenHeit

1
@Pablo hiçbir şey eksik değil. Ancak, tüm satır artık belirsiz olmasa bile, netleşmeyi yazmak gerekiyordu.
Johannes Schaub - litb

1
@Pablo, dili ve derleyicileri daha basit tutmaktır. Anahtar kelimeye daha az ihtiyaç duymanız için, işleri otomatik olarak çözecek daha fazla duruma izin veren teklifler vardır. Örneğinizde , simgenin belirsiz olduğunu ve yalnızca iki kez sonra ">" ifadesini gördükten sonra, şablon köşeli ayraç olarak belirsizleştirebileceğinizi unutmayın. Daha fazla ayrıntı için, sormak yanlış kişiyim, çünkü C ++ derleyicilerinin ayrıştırıcısını uygulama konusunda deneyimim yok.
Johannes Schaub - litb

135

C ++ 11

Sorun

C ++ 03'teki kurallar ne zaman ihtiyacınız olduğu typenameve templatebüyük ölçüde makul olsa da, formülasyonunun can sıkıcı bir dezavantajı var

template<typename T>
struct A {
  typedef int result_type;

  void f() {
    // error, "this" is dependent, "template" keyword needed
    this->g<float>();

    // OK
    g<float>();

    // error, "A<T>" is dependent, "typename" keyword needed
    A<T>::result_type n1;

    // OK
    result_type n2; 
  }

  template<typename U>
  void g();
};

Görülebileceği gibi, derleyici kendini A::result_typesadece int(ve dolayısıyla bir tür) this->golabilecek ve sadece gdaha sonra bildirilen üye şablonu ( Aaçıkça bir yerde uzmanlaşmış olsa bile , bu şablon içindeki kodu etkilemez, bu yüzden anlamı daha sonraki bir uzmanlıktan etkilenemez A!).

Mevcut örnekleme

Durumu iyileştirmek için, C ++ 11'de bir tür ekteki şablonu ifade ettiğinde dil izler. Bilmek için, türünün kendi adıdır ismin belli bir form, (yukarıdaki kullanılarak oluşturulmuş olması gerekir A, A<T>, ::A<T>). Böyle bir adla başvurulan bir türün geçerli örnekleme olduğu bilinmektedir . Adın oluşturulduğu tür bir üye / iç içe sınıfsa (o zaman A::NestedClassve Aher ikisi de geçerli örneklerdir) , tüm geçerli örnekleme olan birden çok tür olabilir .

Bu kavramına dayanarak, dil söyler CurrentInstantiation::Foo, Foove CurrentInstantiationTyped->Foo(örneğin, A *a = this; a->Foohepsi) geçerli örnekleme üyesi eğer onlar sadece yaparak cari örnekleme veya bağımlı olmayan baz sınıftan biridir bir sınıfın üyeleri (olduğu tespit edilmiştir hemen adı arayın).

Anahtar kelimeler typenameve templateniteleyici geçerli örneğin bir üyesi ise artık gerekli değildir. Burada hatırlanması gereken kilit nokta A<T>, hala türe bağlı bir isim olmasıdır (sonuçta Ttüre de bağlıdır). Ancak A<T>::result_typebir tür olduğu bilinmektedir - derleyici bunu anlamak için bu tür bağımlı türlere "sihirli bir şekilde" bakacaktır.

struct B {
  typedef int result_type;
};

template<typename T>
struct C { }; // could be specialized!

template<typename T>
struct D : B, C<T> {
  void f() {
    // OK, member of current instantiation!
    // A::result_type is not dependent: int
    D::result_type r1;

    // error, not a member of the current instantiation
    D::questionable_type r2;

    // OK for now - relying on C<T> to provide it
    // But not a member of the current instantiation
    typename D::questionable_type r3;        
  }
};

Bu etkileyici, ama daha iyisini yapabilir miyiz? Dil daha da ileri gider ve somutlaştırılırken bir uygulamanın tekrar aranmasını gerektirir (anlamını zaten tanım zamanında bulmuş olsa bile). Şimdi arama sonucu farklı olduğunda veya belirsizlikle sonuçlandığında, program kötü biçimlendirilmiştir ve bir tanı verilmelidir. Belirlediğimiz ne olur düşünün böyleD::result_typeD::fC

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

Örnek oluştururken hatayı yakalamak için bir derleyici gerekir D<int>::f. Böylece iki dünyanın en iyisini elde edersiniz: "Gecikmeli" arama, bağımlı temel sınıflarla ilgili sorun yaşarsanız sizi korur ve ayrıca sizi typenameve ' dan kurtaran "Hemen" arama template.

Bilinmeyen uzmanlıklar

Kodunda, Dad typename D::questionable_type, geçerli örneğin bir üyesi değildir. Bunun yerine dil , bilinmeyen bir uzmanlığın üyesi olarak işaretler . Özellikle, bu yapıyorsun durum her zaman DependentTypeName::Fooya DependentTypedName->Foove ya bağımlı türüdür değil şimdiki örnekleme (bu durumda derleyici kadar verebilirsiniz ve daha sonra ne bakacağız" demek Fooolduğu) ya da bunun bir akım örnekleme ve adı veya bağımlı olmayan temel sınıflarında bulunamadı ve ayrıca bağımlı temel sınıfları da vardır.

hYukarıda tanımlanan Asınıf şablonunda bir üye fonksiyonumuz olsaydı ne olacağını hayal edin

void h() {
  typename A<T>::questionable_type x;
}

C ++ 03'te, dilin bu hatayı yakalamasına izin verildi çünkü hiçbir zaman geçerli bir yol olamazdı A<T>::h(verdiğiniz argüman T). C ++ 11'de, dilin derleyicilere bu kuralı uygulaması için daha fazla neden vermek için daha fazla denetimi vardır. Yana Ahiçbir bağımlı baz sınıfları vardır ve Ahiçbir üyeyi beyan questionable_type, adı A<T>::questionable_typeolduğunu ne şimdiki örnekleme üyesi ne debilinmeyen bir uzmanlığın üyesi. Bu durumda, söz konusu kodun örnekleme zamanında geçerli olarak derlenebilmesinin hiçbir yolu olmamalıdır, bu nedenle dil, niteleyicinin ne olduğu bilinmeyen bir uzmanlığın üyesi ne de mevcut örneklemenin bir üyesi olması gereken bir adı yasaklar (ancak , bu ihlalin hala teşhis edilmesine gerek yoktur).

Örnekler ve bilgiler

Bu bilgiyi bu cevapta deneyebilir ve yukarıdaki tanımların sizin için gerçek dünyadaki bir örnek üzerinde anlamlı olup olmadığını görebilirsiniz (bu cevapta biraz daha az ayrıntılı tekrarlanırlar).

C ++ 11 kuralları, aşağıdaki geçerli C ++ 03 kodunu kötü biçimlendirir (C ++ komitesi tarafından tasarlanmamış, ancak muhtemelen düzeltilmeyecektir)

struct B { void f(); };
struct A : virtual B { void f(); };

template<typename T>
struct C : virtual B, T {
  void g() { this->f(); }
};

int main() { 
  C<A> c; c.g(); 
}

Bu, geçerli C ++ 03 kod bağlamak istiyorum this->fiçin A::förnekleme zamanda ve her şey yolunda. Bununla birlikte, C ++ 11 anında bağlanır B::fve somutlaştırılırken aramanın hala eşleşip eşleşmediğini kontrol eder. Başlatmasını Ancak C<A>::g, Hakimiyet Kural uygular ve arama bulacaksınız A::fyerine.


fyi - bu yanıta burada atıfta bulunulur: stackoverflow.com/questions/56411114/… Bu yanıttaki kodun çoğu çeşitli derleyicilerde derlenmemiştir.
Adam Rackis

@AdamRackis, 2013'ten bu yana (bu cevabı yazdığım tarih) C ++ spesifikasyonunun değişmediğini varsayarsak, kodunuzu denediğiniz derleyiciler bu C ++ 11 + özelliğini henüz uygulamaz.
Johannes Schaub - litb

99

ÖNSÖZ

Bu yazının litb'nin yazısına okunması kolay bir alternatif olması bekleniyor .

Temel amaç aynıdır; "Ne zaman?" ve neden?" typenameve templateuygulanmalıdır.


Amacı typenameve amacı nedir template?

typenameve templateşablon bildirme durumu dışındaki durumlarda kullanılabilir.

C ++ 'da derleyiciye açıkça bir adın nasıl ele alınacağının söylenmesi gereken belirli bağlamlar vardır ve tüm bu bağlamların ortak bir yanı vardır; en az bir şablon parametresine bağlıdır .

Yorumlamada belirsizliğin olabileceği bu tür isimlerden; msgstr " bağımlı isimler ".

Bu yazı, bağımlı isimler ve iki anahtar kelime arasındaki ilişkiye bir açıklama sunacaktır .


Bir SNIPPET 1000 KELİMDEN DAHA FAZLA diyor

Kendinize, bir arkadaşınıza veya belki de kedinize aşağıdaki işlev şablonunda neler olduğunu açıklamaya çalışın ; ( A ) işaretli ifadede neler oluyor ?

template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }


Birinin düşündüğü kadar kolay olmayabilir, daha spesifik olarak ( A ) değerlendirmesinin sonucu büyük ölçüde şablon parametresi olarak geçirilen türün tanımına bağlıdırT .

Farklı Ts, ilgili semantiği büyük ölçüde değiştirebilir.

struct X { typedef int       foo;       }; /* (C) --> */ f_tmpl<X> ();
struct Y { static  int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();


İki farklı senaryo :

  • İşlev şablonunu ( C ) 'de olduğu gibi X türüyle başlatırsak, x adında bir işaretçi-int bildirimi alırız ;

  • Bu çalışmada, tip şablonu örneğini eğer Y (olduğu gibi, D ), ( A ) kullanılarak hesaplar ürünü olduğu bir ifade oluşacak 123 bazı ile çarpılan önce değişken ilan x .



Gerekçe

C ++ Standardı en azından bu durumda güvenliğimizi ve refahımızı önemser.

Potansiyel olarak kötü sürprizler muzdarip bir uygulama önlemek için, Standart görev bir nevi bir belirsizlik Çek şu bağımlı-name tarafından açıkça niyet hiçbir yerinde belirten bir ya olarak adını tedavi etmek istiyorum tip-name ya da bir template- id .

Hiçbir şey belirtilmezse, bağımlı ad bir değişken veya bir işlev olarak kabul edilecektir.



BAĞIMLI İSİMLER NASIL KULLANILIR ?

Eğer bu bir Hollywood filmi olsaydı , bağımlı isimler vücut temasıyla yayılan hastalık olurdu, aniden kafasını karıştıracak şekilde etkiler. Muhtemelen kötü biçimlendirilmiş bir perso-, erhm .. programına yol açabilecek karışıklık.

Bir bağımlı, adı olan herhangi bir doğrudan ya da dolaylı olarak, bir bağlıdır adı şablon parametresi .

template<class T> void g_tmpl () {
   SomeTrait<T>::type                   foo; // (E), ill-formed
   SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
   foo.data<int> ();                         // (G), ill-formed    
}

Yukarıdaki snippet'te dört bağımlı ismimiz var:

  • E )
    • "tip"SomeTrait<T> , aşağıdakileri içeren örneklemeye bağlıdır Tve;
  • F )
    • Şablon kimliği olan "NestedTrait" , aşağıdakilere bağlıdır SomeTrait<T>ve;
    • ( F ) ' nin sonundaki "tip" , aşağıdakilere bağlı olan NestedTrait'a bağlıdır SomeTrait<T>ve;
  • G )
    • üye işlevi şablonuna benzeyen "veri" dolaylı olarak bağımlı bir isimdir çünkü foo türü , örneğinin örneğine bağlıdır SomeTrait<T>.

Derleyici bağımlı adları değişkenler / işlevler olarak yorumlayacaksa ( E ), ( F ) veya ( G ) ifadelerinin hiçbiri geçerli değildir (daha önce belirtildiği gibi, aksi açıkça söylemezsek ne olur).

ÇÖZÜM

Yapmak için g_tmplbiz (bir tür beklediklerini biz açıkça derleyici söylemek gerekir geçerli bir tanımı var E ), bir şablon kimliği ve tür (içinde F ) ve bir şablon kimliği de ( G ).

template<class T> void g_tmpl () {
   typename SomeTrait<T>::type foo;                            // (G), legal
   typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
   foo.template data<int> ();                                  // (I), legal
}

Bir ad bir türü her ifade ettiğinde, tümü ettiğinde, ilgili adların tür-adları veya ad alanları olması gerekir; bunun akılda tutulmasıyla typename, tam adımızın başında başvurduğumuzu görmek oldukça kolaydır .

template ancak bu konuda farklıdır, çünkü şu sonuca varmanın bir yolu yoktur; "oh, bu bir şablon, o zaman bu başka şey de bir şablon olmalı" . Bu, bu şekilde ele almak istediğimiz templateherhangi bir adın önüne doğrudan başvurduğumuz anlamına gelir .



CAN I SADECE Stick ANAHTAR KELİMELER HERHANGİ ADI ÖN?

" Sadece yapışabilir miyim typenameve templateherhangi bir ismin önüne? Görünüşleri bağlam hakkında endişelenmek istemiyorum ... " -Some C++ Developer

Standarttaki kurallar, nitelikli bir adla ( K ) uğraştığınız sürece anahtar kelimeleri uygulayabileceğinizi belirtir , ancak ad nitelikli değilse uygulama kötü biçimlendirilir ( L ).

namespace N {
  template<class T>
  struct X { };
}

         N::         X<int> a; // ...  legal
typename N::template X<int> b; // (K), legal
typename template    X<int> c; // (L), ill-formed

Not : Gerekli olmadığı bir bağlamda typenameveya templatebağlamda başvuru yapmak iyi uygulama olarak değerlendirilmez; sadece bir şeyler yapabileceğiniz için yapmanız gerektiği anlamına gelmez.


Ayrıca burada typenamevetemplate , açıkça izin verilmeyen açıkça izin :

  • Bir sınıfın miras aldığı temelleri belirtirken

    Türetilmiş bir sınıfın temel belirtici listesinde yazılan her ad , zaten bir tür adı olarak ele alınır ve açıkça belirtmek typenamehem yanlış biçimlendirilmiş hem de gereksizdir.

                       // .------- the base-specifier-list
     template<class T> // v
     struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
       ...
     };


  • Ne zaman şablon kimliği biri türetilen sınıfın içinde sevk edilirken kullanarak-yönergesi

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };

20
typedef typename Tail::inUnion<U> dummy;

Ancak, inUnion uygulamasının doğru olduğundan emin değilim. Doğru anlarsam, bu sınıfın somutlaştırılmaması gerekir, bu nedenle "başarısız" sekmesi asla başarısız olmaz. Belki de türün birleşimde olup olmadığını basit bir boole değeriyle belirtmek daha iyi olur.

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

Not: Bir göz atın :: Variant

PS2: Tipik listelere bir göz atın , özellikle Andrei Alexandrescu'nun kitabında: Modern C ++ Design


örneğin U == int ile Union <float, bool> :: operator = (U) öğesini çağırmayı denerseniz, inUnion <U> somutlaştırılır. Özel bir küme çağırır (U, inUnion <U> * = 0).
MSalters

Sonuç = true / false ile çalışmak, şu anki OSX araç zincirimizle uyumsuz olan boost :: enable_if <> 'ye ihtiyacım olması. Ayrı şablon yine de iyi bir fikirdir.
MSalters

Luc, typedef Tail :: inUnion <U> kukla anlamına gelir; hat. Kuyruk'u başlatacak. ancak unun <U> değil. tam tanımına ihtiyaç duyduğunda somutlaşır. örneğin sizeof'i alırsanız veya bir üyeye erişirseniz (:: foo kullanarak) olur. @MSalters neyse, başka bir sorununuz var:
Johannes Schaub - litb

-sizeof (U) asla negatif değildir :) çünkü size_t imzasız bir tamsayı türüdür. çok yüksek bir sayı elde edersiniz. muhtemelen sizeof (U)> = 1? -1: 1 veya benzeri :)
Johannes Schaub - litb

i sadece tanımsız bırakın ve sadece beyan: şablon <typename U> struct inUnion; bu yüzden kesinlikle örneklenemez. i derleyici size bile bir hata vermek için de izin verilir sizeof ile sahip düşünüyorum değildir sizeof (U) bilir çünkü eğer bunu örneğini hep> = 1 ve ...
Johannes Schaub - LITB

20

Bu cevap, başlıklı soruyu (bir kısmını) cevaplamak için oldukça kısa ve tatlı bir cevaptır. Bunları neden oraya koymanız gerektiğini açıklayan daha ayrıntılı bir cevap istiyorsanız, lütfen buraya gidin .


typenameAnahtar kelimeyi koymanın genel kuralı çoğunlukla bir şablon parametresi kullandığınızda ve iç içe typedefveya takma ad kullanmak istediğinizde , örneğin:

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

Bunun meta işlevler veya genel şablon parametrelerini alan şeyler için de geçerli olduğunu unutmayın. Ancak, sağlanan template parametresi açık bir türse typename, örneğin belirtmeniz gerekmez :

template<typename T>
struct test {
    // typename required
    using type = typename std::conditional<true, const T&, T&&>::type;
    // no typename required
    using integer = std::conditional<true, int, float>::type;
};

Niteleyiciyi eklemek için genel kurallar templateçoğunlukla benzerdir, ancak tipik olarak kendisi şablonlanmış bir yapının / sınıfın şablonlu üye işlevlerini (statik veya başka türlü) içermesi dışında, örneğin:

Bu yapı ve işlev göz önüne alındığında:

template<typename T>
struct test {
    template<typename U>
    void get() const {
        std::cout << "get\n";
    }
};

template<typename T>
void func(const test<T>& t) {
    t.get<int>(); // error
}

t.get<int>()Fonksiyonun içinden erişmeye çalışmak bir hatayla sonuçlanır:

main.cpp:13:11: error: expected primary-expression before 'int'
     t.get<int>();
           ^
main.cpp:13:11: error: expected ';' before 'int'

Bu nedenle, bu bağlamda templateönceden anahtar kelimeye ihtiyacınız olacak ve şöyle adlandırmalısınız:

t.template get<int>()

Bu şekilde derleyici bunu düzgün bir şekilde ayrıştırır t.get < int.


2

Konuyla ilgili okuduğum en kısa açıklama olduğu için JLBorges'in benzer bir soruya cplusplus.com'dan verdikleri mükemmel yanıtı veriyorum.

Yazdığımız bir şablonda, kullanılabilecek iki tür ad vardır - bağımlı adlar ve bağımlı olmayan adlar. Bağımlı ad, şablon parametresine bağlı bir addır; bağımlı olmayan bir ad, şablon parametrelerinin ne olduğuna bakılmaksızın aynı anlama sahiptir.

Örneğin:

template< typename T > void foo( T& x, std::string str, int count )
{
    // these names are looked up during the second phase
    // when foo is instantiated and the type T is known
    x.size(); // dependant name (non-type)
    T::instance_count ; // dependant name (non-type)
    typename T::iterator i ; // dependant name (type)

    // during the first phase, 
    // T::instance_count is treated as a non-type (this is the default)
    // the typename keyword specifies that T::iterator is to be treated as a type.

    // these names are looked up during the first phase
    std::string::size_type s ; // non-dependant name (type)
    std::string::npos ; // non-dependant name (non-type)
    str.empty() ; // non-dependant name (non-type)
    count ; // non-dependant name (non-type)
}

Bağımlı bir adın ifade ettiği şey, şablonun her farklı örneği için farklı bir şey olabilir. Sonuç olarak, C ++ şablonları "iki aşamalı ad aramasına" tabidir. Bir şablon başlangıçta ayrıştırıldığında (herhangi bir örnekleme gerçekleşmeden önce) derleyici bağımlı olmayan adları arar. Şablonun belirli bir örneği gerçekleştiğinde, şablon parametreleri o zamana kadar bilinir ve derleyici bağımlı isimlere bakar.

İlk aşama sırasında, ayrıştırıcının bağımlı bir adın bir türün adı mı yoksa türün olmayan bir adı mı olduğunu bilmesi gerekir. Varsayılan olarak, bağımlı bir adın tür olmayan bir ad olduğu varsayılır. Bağımlı bir addan önceki typename anahtar sözcüğü, bunun bir türün adı olduğunu belirtir.


özet

Anahtar kelime türünü, bir türe başvuran ve bir şablon parametresine bağlı olan nitelikli bir adınız varsa, yalnızca şablon bildirimlerinde ve tanımlarında kullanın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.