Bu şablon işlevi neden beklendiği gibi davranmıyor?


23

Şablon işlevleri hakkında okuyordum ve bu sorunla karıştım:

#include <iostream>

void f(int) {
    std::cout << "f(int)\n";
}

template<typename T>
void g(T val) {
    std::cout << typeid(val).name() << "  ";
    f(val);
}

void f(double) {
    std::cout << "f(double)\n";
}

template void g<double>(double);

int main() {
    f(1.0); // f(double)
    f(1);   // f(int)
    g(1.0); // d  f(int), this is surprising
    g(1);   // i  f(int)
}

Yazmazsam sonuçlar aynı template void g<double>(double);.

Ben düşünüyorum g<double>sonra örneği alınmalıdır f(double)ve bu nedenle çağrısı fiçinde gçağırmalıdır f(double). Şaşırtıcı bir şekilde, yine çağırır f(int)içinde g<double>. Biri bunu anlamama yardımcı olabilir mi?


Cevapları okuduktan sonra kafa karışıklığımın gerçekte ne olduğunu anladım.

İşte güncellenmiş bir örnek. Çoğunlukla değişmedi, ancak aşağıdakiler için bir uzmanlık ekledim g<double>:

#include <iostream>

void f(int){cout << "f(int)" << endl;}

template<typename T>
void g(T val)
{
    cout << typeid(val).name() << "  ";
    f(val);
}

void f(double){cout << "f(double)" << endl;}

//Now use user specialization to replace
//template void g<double>(double);

template<>
void g<double>(double val)
{
    cout << typeid(val).name() << "  ";
    f(val);
}

int main() {
    f(1.0); // f(double)
    f(1);  // f(int)
    g(1.0); // now d  f(double)
    g(1);  // i  f(int)
}

Kullanıcı uzmanlığı ile g(1.0)beklediğim gibi davranır.

Derleyici bu aynı örneği otomatik olarak g<double>aynı yerde yapmamalı mı (hatta C ++ Programlama Dili , 4. baskı main()bölüm 26.3.3'te açıklandığı gibi )?


3
Son çağrı, bana g(1)veriyor i f(int). Sen yazdın d f(double). Bu bir yazım hatası mıydı?
HTNW

Evet. afedersiniz. güncellendi
Zhongqi Cheng

Şablonun temel ilkesi, kullanıcı tarafından bildirilen sembollerle dahili kütüphane çağrılarının ele geçirilmesini engellerken, kullanıcı türlerinde işlemlerin kullanımını desteklemektir. Bu imkansız bir uzlaşmadır, çünkü şablonlar için "konsept" sözleşmeler yoktur ve bu tür sağlam "sözleşmeleri" tanıtmak için çok geç.
curiousguy

Yanıtlar:


12

Ad f, bağımlı bir addır ( Tbağımsız değişkene bağlıdır val) ve iki adıma çözümlenir :

  1. ADL dışı arama , şablon tanımı bağlamından görülebilen işlev bildirimlerini inceler .
  2. ADL , şablon tanımı bağlamından veya şablon örneği bağlamından görülebilen işlev bildirimlerini inceler .

void f(double)şablon tanımı içeriğinden görünmez ve ADL de bulamaz, çünkü

Temel tür argümanları için, ilişkili ad alanları ve sınıflar kümesi boş


Örneğinizi biraz değiştirebiliriz:

struct Int {};
struct Double : Int {};

void f(Int) { 
    std::cout << "f(Int)";
}

template<typename T>
void g(T val) {
    std::cout << typeid(val).name() << ' ';
    f(val);
    // (f)(val);
}

void f(Double) { 
    std::cout << "f(Double)";
}

int main() {
    g(Double{});
}

Şimdi ADL void f(Double)ikinci adımda bulacaktır ve çıktı olacaktır 6Double f(Double). ADL yerine yazarak (f)(val)(veya ::f(val)) devre dışı bırakabiliriz f(val). Daha sonra çıktı, 6Double f(Int)örneğinizle uyumlu olacaktır.


Çok teşekkür ederim. G <double> için örnekleme kodunda nerede olduğunu merak ediyorum. Main () 'den hemen önce mi? Öyleyse, somutlaştırılmış g <double> tanımı hem f (int) hem de f (çift) 'i göremiyor ve nihayet f (çift)' i seçemiyor mu?
Zhongqi Cheng

@ZhongqiCheng 1. adımda yalnızca şablon tanımı bağlamı dikkate alınacaktır ve bu bağlamdan void f(double)görünmez - bu bağlam, beyanından önce sona ermektedir. 2. adımda ADL hiçbir şey bulamaz, bu nedenle şablon örnekleme içeriği burada herhangi bir rol oynamaz.
Evg

@ZhongqiCheng, düzenlemenizde daha sonra bir tanım sundunuz, void f(double)bu nedenle bu işlev ondan görülebilir. Şimdi fbağımlı bir isim değil. Eğer f(val);tanımından sonra beyan için daha iyi bir eşleşme varsa g<double>, o da bulunmaz. "İleriye bakmanın" tek yolu ADL'dir (veya iki aşamalı aramayı doğru şekilde uygulamayan eski bir derleyici).
Evg

İşte cevabımı anladım. İşlev şablonlarının (g <int> ve g <double>) şablon tanımından hemen sonra başlatıldığını varsaymalıyım. Bu nedenle f (çift) görmeyecek. Bu doğru mu. Çok teşekkür ederim.
1919'da Zhongqi Cheng

@ZhongqiCheng, hemen önce başlatıldı main(). Onlar görmez f(double)arama faz biri zaten yapılmıştır ve hiçbir bulmuştur: örnekleme gerçekleştiğinde, çok geç olduğu için, f(double).
Evg

6

Sorun, f(double)aradığınız noktada bildirilmemiştir; eğer beyanını önüne taşırsanız, template gçağrılacaktır.

Düzenleme: Neden manuel örnekleme kullanılır?

(Yalnızca işlev şablonları hakkında konuşacağım, sınıf şablonları için de benzer argüman tutulur.) Ana kullanım, derleme sürelerini azaltmak ve / veya şablonun kodunu kullanıcılardan gizlemektir.

C ++ programı 2 adımda ikili dosyalara yerleştirilmiştir: derleme ve bağlantı. Bir fonksiyonun derlenmesi için sadece fonksiyonun başlığı gereklidir. Bağlamanın başarılı olması için, işlevin derlenmiş gövdesini içeren bir nesne dosyası gerekir.

Derleyici şablonlanmış bir işlevin çağrısını gördüğünde , yaptığı şey şablonun gövdesini mi yoksa yalnızca üstbilgiyi mi bildiğine bağlıdır. Yalnızca üstbilgiyi görürse, işlevin şablonlaştırılmamış haliyle aynı şeyi yapar: bağlayıcı dosyasına yapılan çağrıyla ilgili bilgileri nesne dosyasına koyar. Ancak şablonun gövdesini de görürse başka bir şey yapar: vücudun uygun örneğini başlatır, bu gövdeyi derler ve nesne dosyasına da koyar.

Birden çok kaynak dosya, şablonlanmış işlevin aynı örneğini çağırırsa, nesne dosyalarının her biri, işlevin örneğinin derlenmiş bir sürümünü içerir. (Linker bunu bilir ve tek bir derlenmiş işleve yapılan tüm çağrıları çözer, bu nedenle programın / kütüphanenin son ikili dosyasında yalnızca bir tane olacaktır.) Ancak, kaynak dosyaların her birini derlemek için işlevin somutlaştırılması ve derledi, hangi zaman aldı.

Fonksiyonun gövdesi bir nesne dosyasındaysa, bağlayıcının işini yapması yeterlidir. Şablonu kaynak dosyaya el ile başlatmak, derleyicinin işlevin gövdesini söz konusu kaynak dosyanın nesne dosyasına koymasını sağlamanın bir yoludur. (Bu işlev çağrılmış gibi olabilir, ancak örnekleme işlev çağrısının geçersiz olacağı bir yere yazılır.) Bu yapıldığında, işlevinizi çağıran tüm dosyalar yalnızca işlevin başlığını bilerek derlenebilir. fonksiyonun gövdesini her bir çağrıda somutlaştırmak ve derlemek zaman alır.

İkinci neden (uygulama gizleme) şimdi mantıklı olabilir. Bir kütüphane yazarı, şablon işlevinin kullanıcılarının işlevi kullanabilmesini isterse, genellikle onlara şablonun kodunu verir, böylece kendileri derleyebilirler. Şablonun kaynak kodunu gizli tutmak isterse, kütüphaneyi oluşturmak ve kullanıcılara kaynak yerine bu şekilde elde edilen nesne sürümünü vermek için şablonu kodda manuel olarak başlatabilir.

Bu bir anlam ifade ediyor mu?


Yazarın ilk kodunda sunulan örnekleme ile kurgudan sonra yazarın ikinci kodundaki uzmanlık arasındaki farkı açıklayabilirseniz minnettar olurum. Uzmanlık, örnekleme ve kitaplar hakkında birçok kez siteyi okudum, ama anlamadım. Thank you
Dev

@Dev: Lütfen sorunuzu biraz daha belirtin, ne cevap vereceğinden emin değilim. Temel olarak bu durumda fark, uzmanlaşma mevcut olduğunda derleyicinin onu kullanması, mevcut olmadığı zaman, derleyicinin şablonu alması, bir örneği oluşturması ve bu oluşturulan örneği kullanmasıdır. Yukarıdaki kodda hem uzmanlık hem de şablon örneği aynı koda yol açar.
AshleyWilkes

Sorum tam olarak kodun bu kısmına bitişiktir: "template void g <double> (double);" Bunu biliyorsanız, programlama şablonunda örnekleme olarak adlandırılır. Uzmanlık biraz farklıdır, çünkü ikinci kodda olduğu gibi yazarın "template <> void g <double> (double val) {cout << typeid (val) .name () <<" "; f ( val);} "Bana farkı açıklayabilir misiniz?
Dev

@Dev Bunu zaten yapmaya çalıştım: derleyici eğer yapabilirse bir uzmanlık kullanır; uzmanlığı göremiyorsa (ör. hiçbiri olmadığından) derleyici şablonun bir örneğini oluşturur ve bu örneği kullanır. Yukarıdaki kodda hem şablon hem de uzmanlık aynı sonuca götürür, bu yüzden tek fark derleyicinin bu sonuca ulaşmak için yaptığı şeydir. Diğer durumlarda uzmanlık herhangi bir uygulama içerebilir, şablonla ortak bir şeye sahip olması gerekmez (ancak yöntem başlığı için). Daha net?
AshleyWilkes

1
template void g<double>(double);Böylece el örneğinin (not adlandırılan templatesözdizimi ayırt edici bir özelliktir bir köşeli parantez ile); derleyiciye yöntemin bir örneğini oluşturmasını söyler. Burada çok az etkisi vardır, eğer orada değilse, derleyici örneği çağrıldığı yerde oluşturur. Manuel örnekleme nadiren kullanılır özelliği, bir şeyin şimdi daha net olduğunu onayladıktan sonra neden kullanmak isteyebileceğinizi söyleyeceğim :-)
AshleyWilkes
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.