来自Josuttis:不同的模板函数,实例化给定特定类型的相同函数签名,会导致ODR无效吗?

Dan*_*aum 13 c++ templates

在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()直接从main()内调用

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

从g()中调用的f1()

请注意,f1()在两种情况下编译器选择的地址都是相同的 - 13E11EAh.对我来说,这表明实际上,编译器无法区分两个实例化的函数签名,这是ODR被违反的情况,因此代码无效C++和Josuttis在他的书中有错误.但它只是 - 一个迹象.我不知道.

(我已经检查了本书网站上的勘误表,但没有提到这个例子.)

ADDENDUM根据评论的请求,我附加了该程序的.map文件的相关输出,该输出显示了用于以下内容的损坏名称f1:

.map文件输出显示<code> f1 </ code>的错位名称

附录2现在问题得到解答 - Josuttis的书是正确的 - 我想要注意,在Josuttis文本中,在同一部分(12.2.1)中,它明确地概述了什么决定了一个独特的函数签名,包括模板方面.

从文本(在另一个中,定义函数签名的预期事物)中,TRANSLATION UNIT是函数签名的一部分; 对于模板函数(仅),RETURN TYPE是函数签名的一部分,和

0.6.模板参数和模板参数,如果函数是从函数模板生成的.

因此 - 很清楚.即使在实例化了函数模板之后,编译器也必须维护和跟踪模板信息,以便编译器/链接器遵守模板的必要特殊规则(如我的问题中的代码示例).

Ker*_* SB 8

为早先的错误答案道歉.这个例子看起来确实是正确的,并且标准本身实际上有一个类似的例子(C++ 11,14.5.6.1/1-2).我只想完整地引用它:

  1. 可以重载函数模板,以便两个不同的函数模板特化具有相同的类型.[ 例如:

    // 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*)
    }
    
    Run Code Online (Sandbox Code Playgroud)

    - 结束例子 ]

  2. 这种专业化是不同的功能,不违反一个定义规则(3.2).

在您的情况下,您有两个不同的函数模板,都被调用f1(这很好,因为您可以重载函数模板),并且它们碰巧具有相同类型的特化.

  • 此外,模板实例化(而不是显式sprcializations)不是TU的一部分.它们是IU的一部分,它是实例化单元. (2认同)