在Josuttis和Vandevoorde着名的模板书C++模板:完整指南中,他们讨论了有关函数模板重载的细节.
在他们的一个示例中,与函数签名和重载函数模板的讨论相关,它们呈现了他们用以下术语描述的代码:
This program is valid and produces the following output:
(Note: Output shown below)
Run Code Online (Sandbox Code Playgroud)
但是,当我在Visual Studio 2010中构建和编译相同的代码时,我得到了不同的结果.这让我相信VS 2010编译器产生的代码不正确,或者Josuttis代码有效是不正确的.
这是代码.(Josuttis 2003,Section 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();
}
Run Code Online (Sandbox Code Playgroud)
...
// 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');
}
Run Code Online (Sandbox Code Playgroud)
(注意两个模板函数定义中类型参数的反转.另请注意,当两个类型参数相同时,此反转无效,因为它们适用f1()于此代码示例中的两个函数.)
Josuttis说:
This program is valid and produces the following output:
f1(T2, T1)
f1(T1, T2)
Run Code Online (Sandbox Code Playgroud)
当我在Visual Studio 2010编译器中构建并运行相同的代码(未更改)时,这是我的结果:
f1(T1, T2)
f1(T1, T2)
Run Code Online (Sandbox Code Playgroud)
此外,我想知道编译器/链接器如何区分f1file1.cpp中实例化的函数f1和file2.cpp中实例化的函数,假设(我认为)编译器剥离了所有"知识"事实上这些函数是从模板创建的,并且只有函数签名本身的信息(我认为):void (char, char)这两个f1函数都是相同的.
由于(如果我是正确的)函数签名在两个翻译单元中是相同的,我认为这是违反单定义规则(ODR)的一个例子,因此它将是无效的C++.
但是,正如我刚才所说,Josuttis和Vandevoorde声称这是有效的 C++.
但由于我编译的相同代码版本给出的结果与Josuttis声称的输出结果不同,这似乎表明VS 2010产生的代码不正确,或者Josuttis在这种情况下不正确(即代码无效且违反ODR).
Josuttis和Vandevoorde是不正确的,还是VS 2010产生的输出不正确?或者是否有其他解释可以解释VS 2010产生的输出与Josuttis报告的输出之间的差异?
可能有兴趣在每个f1()被调用时显示VS 2010反汇编.
f1()(直接在内main())的第一个电话:

f1()(来自内部g())的第二次调用:

请注意,f1()在两种情况下编译器选择的地址都是相同的 - 13E11EAh.对我来说,这表明实际上,编译器无法区分两个实例化的函数签名,这是ODR被违反的情况,因此代码无效C++和Josuttis在他的书中有错误.但它只是 - 一个迹象.我不知道.
(我已经检查了本书网站上的勘误表,但没有提到这个例子.)
ADDENDUM根据评论的请求,我附加了该程序的.map文件的相关输出,该输出显示了用于以下内容的损坏名称f1:

附录2现在问题得到解答 - Josuttis的书是正确的 - 我想要注意,在Josuttis文本中,在同一部分(12.2.1)中,它明确地概述了什么决定了一个独特的函数签名,包括模板方面.
从文本(在另一个中,定义函数签名的预期事物)中,TRANSLATION UNIT是函数签名的一部分; 对于模板函数(仅),RETURN TYPE是函数签名的一部分,和
0.6.模板参数和模板参数,如果函数是从函数模板生成的.
因此 - 很清楚.即使在实例化了函数模板之后,编译器也必须维护和跟踪模板信息,以便编译器/链接器遵守模板的必要特殊规则(如我的问题中的代码示例).
为早先的错误答案道歉.这个例子看起来确实是正确的,并且标准本身实际上有一个类似的例子(C++ 11,14.5.6.1/1-2).我只想完整地引用它:
可以重载函数模板,以便两个不同的函数模板特化具有相同的类型.[ 例如:
Run Code Online (Sandbox Code Playgroud)// file1.c template<class T> void f(T*); void g(int* p) { f(p); // calls f<int>(int*) } // file2.c template<class T> void f(T); void h(int* p) { f(p); // calls f<int*>(int*) }- 结束例子 ]
- 这种专业化是不同的功能,不违反一个定义规则(3.2).
在您的情况下,您有两个不同的函数模板,都被调用f1(这很好,因为您可以重载函数模板),并且它们碰巧具有相同类型的特化.