Czytałem o funkcjach szablonów i ten problem mnie pomieszał:
#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)
}
Wyniki są takie same, jeśli nie piszę template void g<double>(double);
.
Myślę, że g<double>
powinien zostać f(double)
utworzony później , a zatem wezwanie do f
w g
powinno zadzwonić f(double)
. Zaskakująco, to nadal nazywa f(int)
się g<double>
. Czy ktoś może mi pomóc to zrozumieć?
Po przeczytaniu odpowiedzi zorientowałem się, jakie jest moje zamieszanie.
Oto zaktualizowany przykład. Jest to w większości niezmienione oprócz tego, że dodałem specjalizację dla 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)
}
Dzięki specjalizacji użytkownika g(1.0)
zachowuje się tak , jak się spodziewałem.
Czy kompilator nie powinien automatycznie wykonywać tej samej instancji g<double>
w tym samym miejscu (lub nawet później main()
, jak opisano w sekcji 26.3.3 języka programowania C ++ , wydanie 4)?
źródło
g(1)
dajei f(int)
mi. Napisałeśd f(double)
. Czy to była literówka?Odpowiedzi:
Nazwa
f
jest nazwą zależną (zależy odT
argumentuval
) i zostanie podzielona na dwa etapy :void f(double)
nie jest widoczny z kontekstu definicji szablonu i ADL też go nie znajdzie, ponieważMożemy nieznacznie zmodyfikować twój przykład:
Teraz ADL znajdzie
void f(Double)
w drugim kroku, a wynik będzie6Double f(Double)
. Możemy wyłączyć ADL, pisząc(f)(val)
(lub::f(val)
) zamiastf(val)
. Wtedy wynik będzie6Double f(Int)
zgodny z twoim przykładem.źródło
void f(double)
nie widać - kontekst ten kończy się przed deklaracją. W kroku 2 ADL niczego nie znajdzie, więc kontekst tworzenia szablonów nie odgrywa tutaj żadnej roli.void f(double)
, więc ta funkcja jest z niej widoczna. Terazf
nie jest to nazwa zależna. Jeślif(val);
po zdefiniowaniu było lepsze dopasowanie do zadeklarowanegog<double>
, również nie zostanie znalezione. Jedynym sposobem na „patrzenie w przyszłość” jest ADL (lub jakiś stary kompilator, który nie implementuje poprawnie wyszukiwania dwufazowego).main()
. Nie zobacząf(double)
, ponieważ gdy wystąpi tworzenie instancji, jest już za późno: faza pierwsza wyszukiwania została już wykonana i nie znalezionof(double)
.Problem
f(double)
nie został zadeklarowany w miejscu, w którym go nazwiesz; jeśli przeniesiesz jego deklarację przedtemplate g
, zostanie wywołane.Edycja: Dlaczego warto stosować ręczne tworzenie instancji?
(Porozmawiam tylko o szablonach funkcji, analogiczna argumentacja dotyczy również szablonów klas.) Głównym zastosowaniem jest skrócenie czasu kompilacji i / lub ukrycie kodu szablonu przed użytkownikami.
Program C ++ jest wbudowany w pliki binarne w 2 krokach: kompilacji i łączenia. Aby kompilacja wywołania funkcji zakończyła się powodzeniem, potrzebny jest tylko nagłówek funkcji. Aby połączenie zakończyło się powodzeniem, potrzebny jest plik obiektowy zawierający skompilowany korpus funkcji.
Teraz, gdy kompilator widzi wywołanie funkcji szablonowej , to, co robi, zależy od tego, czy zna treść szablonu, czy tylko nagłówek. Jeśli widzi tylko nagłówek, robi to samo, jakby funkcja nie była szablonowana: umieszcza informacje o wywołaniu linkera do pliku obiektowego. Ale jeśli widzi również treść szablonu, robi również inną rzecz: tworzy odpowiednią instancję obiektu, kompiluje to ciało i umieszcza je również w pliku obiektowym.
Jeśli kilka plików źródłowych wywołuje to samo wystąpienie funkcji szablonowej, każdy z ich plików obiektowych będzie zawierał skompilowaną wersję wystąpienia funkcji. (Linker wie o tym i rozwiązuje wszystkie wywołania jednej skompilowanej funkcji, więc będzie tylko jedno w końcowym pliku binarnym programu / biblioteki.) Jednak aby skompilować każdy plik źródłowy, funkcja musiała zostać utworzona i skompilowane, co wymagało czasu.
Wystarczy, aby linker wykonał swoją pracę, jeśli treść funkcji znajduje się w jednym pliku obiektowym. Aby ręcznie utworzyć szablon w pliku źródłowym, można zmusić kompilator do umieszczenia treści funkcji w pliku obiektowym danego pliku źródłowego. (To trochę tak, jakby funkcja została wywołana, ale instancja jest zapisywana w miejscu, w którym wywołanie funkcji byłoby niepoprawne.) Po wykonaniu tej czynności wszystkie pliki, które wywołują twoją funkcję, można skompilować, znając tylko nagłówek funkcji, a zatem oszczędność czasu zajęłoby utworzenie i skompilowanie treści funkcji z każdym wywołaniem.
Drugi powód (ukrywanie implementacji) może mieć teraz sens. Jeśli autor biblioteki chce, aby użytkownicy jej funkcji szablonu mogli korzystać z tej funkcji, zwykle przekazuje im kod szablonu, aby mogli sami go skompilować. Jeśli chciała zachować kod źródłowy szablonu w tajemnicy, mogłaby ręcznie utworzyć instancję szablonu w kodzie, którego używa do budowy biblioteki, i dać użytkownikom wersję obiektu uzyskaną w ten sposób zamiast źródła.
Czy to ma jakiś sens?
źródło
template void g<double>(double);
to tak zwana instancja ręczna (zwróć uwagę natemplate
brak nawiasów ostrych, to cecha wyróżniająca składnię); która informuje kompilator o utworzeniu instancji metody. Tutaj ma to niewielki wpływ, gdyby go nie było, kompilator wygenerowałby instancję w miejscu jej wywołania. Ręczna instancja jest rzadko używana, powiem dlaczego możesz chcieć jej użyć po tym, jak potwierdzisz, że sprawa jest teraz jaśniejsza :-)