在 Josuttis 和 Vandevoorde 关于模板的著名书籍C++ Templates: The Complete Guide中,他们讨论了有关函数模板重载的细节。
在他们的一个示例中,与函数签名和重载函数模板的讨论相关,他们提供了用以下术语描述的代码:
This program is valid and produces the following output:
(Note: Output shown below)
但是,当我在 Visual Studio 2010 中构建和编译相同的代码时,我得到了不同的结果。这使我相信 VS 2010 编译器生成了不正确的代码,或者 Josuttis 不正确地认为代码是有效的。
这是代码。(Josuttis 2003,第 12.2.1 节)
// File1.cpp
#include <iostream>
template<typename T1, typename T2>
void f1(T2, T1)
{
std::cout << "f1(T2, T1)" << std::endl;
}
extern void g();
int main()
{
f1<char, char>('a', 'b');
g();
}
...
// File2.cpp
#include <iostream>
template<typename T1, typename T2>
void f1(T1, T2)
{
std::cout << "f1(T1, T2)" << std::endl;
}
void g()
{
f1<char, char>('a', 'b');
}
(请注意两个模板函数定义中类型参数的反转。另请注意,当两个类型参数相同时,这种反转无效,因为它们适用f1()
于此代码示例中的两个函数。)
根据 Josuttis 的说法:
This program is valid and produces the following output:
f1(T2, T1)
f1(T1, T2)
当我在 Visual Studio 2010 编译器中构建并运行相同的代码并保持不变时,结果如下:
f1(T1, T2)
f1(T1, T2)
此外,我想知道编译器/链接器如何区分f1
在 file1.cpp 中实例化的函数和在 file2.cppf1
中实例化的函数,因为(我认为)编译器剥离了所有“知识”事实上,这些函数是从模板创建的,并且只有函数签名本身的信息(我认为):void (char, char)
,这对于两个f1
函数都是相同的。
由于(如果我是正确的)函数签名在两个翻译单元中是相同的,我认为这是违反单一定义规则(ODR) 的一个示例,因此它将是无效的 C++。
然而,正如我刚刚指出的,Josuttis 和 Vandevoorde 声称这是有效的C++。
但是由于我的相同代码的编译版本给出的结果与 Josuttis 声称的输出不同,这似乎表明 VS 2010 产生了不正确的代码,或者 Josuttis 在这种情况下不正确(即代码无效并且违反ODR)。
Josuttis 和 Vandevoorde 是不正确的,还是 VS 2010 产生的输出不正确?还是有其他解释可以解释 VS 2010 产生的输出与 Josuttis 报告的输出之间的差异?
在调用每个函数时显示 VS 2010 的反汇编可能会很有趣f1()
。
f1()
(直接在 内main()
)的第一次调用:
f1()
(来自内部g()
)的第二次调用:
请注意,f1()
编译器在两种情况下选择的地址是相同的 - 13E11EAh。对我来说,这表明实际上编译器无法区分两个实例化的函数签名,这是违反 ODR 的情况,因此代码是无效的 C++并且 Josuttis 在他的书中有错误。但这只是 - 一个迹象。我不知道。
(我在书的网站上查过勘误表,没有提到这个例子。)
附录根据评论的请求,我附上了来自该程序的 .map 文件的相关输出,该输出显示了用于f1
:
附录 2现在问题已得到解答 - Josuttis 的书是正确的 - 我想指出,在 Josuttis 文本的同一部分 (12.2.1) 中,明确概述了确定唯一函数签名的确切因素,包括模板方面。
从文本(以及其他定义函数签名的预期内容)来看,翻译单元是函数签名的一部分;对于模板函数(仅),返回类型是函数签名的一部分,并且
.6. 模板参数和模板参数,如果函数是从函数模板生成的。
因此 - 很明显。即使在函数模板被实例化之后,编译器也必须维护和跟踪模板信息,以便编译器/链接器遵守模板的必要特殊规则(如我问题中的代码示例)。