我正在阅读有关模板函数的信息,并对这个问题感到困惑:
#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)
的行为符合我的预期。
如 The C++ Programming Language第26.3.3节中所述,编译器是否应该不在同一位置(甚至在g<double>
之后,对main()
自动执行相同的实例化,,第4版)?
答案 0 :(得分:11)
名称f
是一个从属名称(它通过参数T
依赖于val
),它将解析为two steps:
- 非ADL查找检查从模板定义上下文可见的函数声明。
- ADL检查从模板定义上下文或模板实例化上下文可见的函数声明。
void f(double)
在模板定义上下文中不可见,ADL也不会找到它,because
对于基本类型的参数,关联的名称空间和类的集合为空
我们可以稍微修改您的示例:
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{});
}
现在,ADL在第二步中将找到void f(Double)
,输出将为6Double f(Double)
。我们可以通过写(f)(val)
(或::f(val)
)而不是f(val)
来禁用ADL。与您的示例一致,输出将为6Double f(Int)
。
答案 1 :(得分:6)
问题在于f(double)
在您调用它的地方尚未声明;如果将其声明移到template g
前面,它将被调用。
编辑:为什么要使用手动实例化?
(我仅讨论功能模板,类模板也适用类似的论据。)主要用途是减少编译时间和/或向用户隐藏模板代码。
C ++程序通过以下两个步骤构建到二进制文件中:编译和链接。为了成功进行函数调用的编译,仅需要函数的标头。为了使链接成功,需要一个包含函数的已编译主体的目标文件。
现在,当编译器看到 templated 函数的调用时,其作用取决于它是否知道模板的主体或仅是标题。如果仅看到标头,则其作用与未对函数进行模板化时相同:将有关链接程序调用的信息放入目标文件。但是,如果它还能看到模板的主体,它还会做另一件事:它将实例化主体的适当实例,编译该主体并将其放入目标文件中。
如果多个源文件调用了模板化函数的相同实例,则它们的每个目标文件都将包含该函数实例的编译版本。 (链接器知道这一点,并将所有调用解析为一个已编译函数,因此在程序/库的最终二进制文件中将只有一个。)但是,为了编译每个源文件,必须实例化该函数并编译,这需要时间。
如果函数的主体位于一个目标文件中,则链接程序就可以完成它的工作。手动实例化源文件中的模板是使编译器将函数的主体放入相关源文件的目标文件中的一种方法。 (这有点像调用了函数,但是实例化是在函数调用无效的地方编写的。)完成此操作后,可以仅在知道函数头的情况下编译所有调用函数的文件。节省了通过每次调用实例化和编译函数主体的时间。
第二个原因(隐藏实现)现在可能有意义。如果图书馆作者希望其模板功能的用户能够使用该功能,则通常会向他们提供模板的代码,以便他们自己进行编译。如果她想对模板的源代码保密,可以在用于构建库的代码中手动实例化模板,并向用户提供由此获得的对象版本而不是源代码。
这有意义吗?