13

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

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

f1() 从 g() 中调用

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

(我在书的网站上查过勘误表,没有提到这个例子。)

附录根据评论的请求,我附上了来自该程序的 .map 文件的相关输出,该输出显示了用于f1

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

附录 2现在问题已得到解答 - Josuttis 的书是正确的 - 我想指出,在 Josuttis 文本的同一部分 (12.2.1) 中,明确概述了确定唯一函数签名的确切因素,包括模板方面

从文本(以及其他定义函数签名的预期内容)来看,翻译单元是函数签名的一部分;对于模板函数(仅),返回类型是函数签名的一部分,并且

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

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

4

1 回答 1

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.2)。

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

于 2012-11-29T16:32:26.837 回答