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

时间:2012-11-29 15:53:14

标签: c++ templates

在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,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();
}

...

// 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)

此外,我想知道编译器/链接器如何区分在file1.cpp中实例化的函数f1和在file2.cpp中实例化的函数f1,给定(我认为)编译器剥离了这些函数是从模板创建的事实的所有“知识”,并且只有函数签名本身的信息(我认为):void (char, char),这是相同的两个f1函数。

由于(如果我是正确的)两个翻译单元中的函数签名是相同的,我认为这是违反One Definition Rule(ODR)的一个例子,因此它将是无效的C ++。

然而,正如我刚才所说,Josuttis和Vandevoorde声称这是有效的 C ++。

但是由于我的相同代码的编译版本提供不同的结果而不是Josuttis声称的输出,这似乎表明VS 2010产生的代码不正确,或者Josuttis在此不正确case(即代码无效并违反ODR)。

Josuttis和Vandevoorde是不正确的,还是VS 2010产生了不正确的输出?或者是否有其他解释可以解释VS 2010产生的输出与Josuttis报告的输出之间的差异?

在调用每个f1()时显示VS 2010反汇编可能会很有意义。

f1()的第一个电话(直接在main()内):

f1() called directly from within main()

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

f1() called from within g()

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

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

ADDENDUM 根据评论的请求,我附加了此程序的.map文件的相关输出,其中显示了用于f1的错位名称:

.map file output showing mangled names for <code>f1</code>

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

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

  

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

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

1 个答案:

答案 0 :(得分: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*)
    }
    
         

    - 结束示例]

  2.   
  3. 此类专业化是不同的功能,不违反一个定义规则(3.2)。
  4.   

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