Я читал о шаблонных функциях и запутался в этой проблеме:
#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)
}
Результаты будут такими же, если я не напишу template void g<double>(double);
.
Я думаю, что g<double>
должен быть создан после f(double)
, и, следовательно, вызов f
в g
должен позвонить f(double)
. Удивительно, но до сих пор вызывает f(int)
в g<double>
. Может ли кто-нибудь помочь мне понять это?
Прочитав ответы, я понял, в чем заключается моя путаница.
Вот обновленный пример. В основном это не изменилось, за исключением того, что я добавил специализацию для 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)
}
С пользовательской специализацией g(1.0)
ведет себя как я ожидал.
Разве компилятор не должен автоматически выполнять эту же реализацию для g<double>
того же самого места (или даже после main()
, как описано в разделе 26.3.3 языка программирования C ++ , 4-е издание)?
g(1)
, даетi f(int)
за меня. Вы написалиd f(double)
. Это была опечатка?Ответы:
Имя
f
является зависимым именем (оно зависит отT
аргументаval
) и будет решено в два этапа :void f(double)
не виден из контекста определения шаблона, и ADL не найдет его, потому чтоМы можем немного изменить ваш пример:
Теперь ADL найдет
void f(Double)
во втором шаге, и вывод будет6Double f(Double)
. Мы можем отключить ADL, написав(f)(val)
(или::f(val)
) вместоf(val)
. Тогда вывод будет6Double f(Int)
, в соответствии с вашим примером.источник
void f(double)
не будет видно - этот контекст заканчивается до его объявления. На шаге 2 ADL ничего не найдет, поэтому контекст создания шаблона здесь не играет никакой роли.void f(double)
, поэтому эта функция видна из него. Теперьf
это не зависимое имя. Если было найдено лучшее совпадение дляf(val);
объявленного после определенияg<double>
, оно также не будет найдено. Единственный способ «заглянуть в будущее» - это ADL (или какой-то старый компилятор, который не реализует двухфазный поиск правильно).main()
. Они не увидятf(double)
, потому что, когда происходит создание экземпляра, уже слишком поздно: первый этап поиска уже завершен, и он не нашелf(double)
.Проблема
f(double)
не была объявлена в том месте, где вы ее называете; если вы передвинете его объявление передtemplate g
, оно будет вызвано.Редактировать: Почему можно использовать ручную реализацию?
(Я буду говорить только о шаблонах функций, аналогичная аргументация применима и к шаблонам классов.) Основное использование - сократить время компиляции и / или скрыть код шаблона от пользователей.
Программа на C ++ встраивается в двоичные файлы в 2 этапа: компиляция и компоновка. Для успешной компиляции вызова функции необходим только заголовок функции. Для успешного связывания необходим объектный файл, содержащий скомпилированное тело функции.
Теперь, когда компилятор видит вызов шаблонной функции, то, что он делает, зависит от того, знает ли он тело шаблона или только заголовок. Если он видит только заголовок, он делает то же самое, как если бы функция не была шаблонизирована: помещает информацию о вызове компоновщика в объектный файл. Но если он также видит тело шаблона, он делает еще одну вещь: он создает соответствующий экземпляр тела, компилирует это тело и также помещает его в объектный файл.
Если несколько исходных файлов вызывают один и тот же экземпляр шаблонной функции, каждый из их объектных файлов будет содержать скомпилированную версию экземпляра функции. (Линкер знает об этом и разрешает все вызовы одной скомпилированной функции, поэтому в конечном двоичном файле программы / библиотеки будет только один). Однако для того, чтобы скомпилировать каждый из исходных файлов, функцию нужно было создать и составлено, что заняло время.
Для компоновщика достаточно выполнить свою работу, если тело функции находится в одном объектном файле. Создание экземпляра шаблона в исходном файле вручную - это способ заставить компилятор поместить тело функции в объектный файл рассматриваемого исходного файла. (Это вроде как если бы функция была вызвана, но создание экземпляра написано в месте, где вызов функции был бы недопустим.) Когда это сделано, все файлы, которые вызывают вашу функцию, могут быть скомпилированы, зная только заголовок функции, таким образом экономия времени, необходимого для создания экземпляра и компиляции тела функции с каждым вызовом.
Вторая причина (скрытие реализации) может иметь смысл сейчас. Если автор библиотеки хочет, чтобы пользователи ее функции шаблона могли использовать эту функцию, она обычно дает им код шаблона, чтобы они могли скомпилировать его самостоятельно. Если она хочет сохранить в тайне исходный код шаблона, она может вручную создать экземпляр шаблона в коде, который она использует для построения библиотеки, и предоставить пользователям версию объекта, полученную таким образом, вместо источника.
Есть ли в этом смысл?
источник
template void g<double>(double);
так называемое ручное создание экземпляра (обратите внимание, чтоtemplate
без угловых скобок это отличительная черта синтаксиса); это говорит компилятору создать экземпляр метода. Здесь это имеет небольшой эффект, если бы его не было, компилятор генерировал бы экземпляр в том месте, где он вызывается. Ручная инстанция - редко используемая функция, я скажу, почему вы можете захотеть использовать ее после того, как подтвердите, что теперь все