冲突的模板定义和ODR

时间:2017-09-21 09:30:52

标签: c++ templates linker language-lawyer one-definition-rule

想象一下我有两个不同的翻译单元a.cpp

的情况
#include <iostream>

double bar();

template <typename T>
T foobar(T t) {
    return t;
}

int main() {
    std::cout << "foobar called from b.cpp: " << bar() << '\n';
    std::cout << "foobar called from a.cpp: " << foobar(1.) << '\n';
}

和b.cpp:

template <typename T>
T foobar(T t) {
    return t + 1.;
}

double bar() {
    return foobar(1.);
}

我知道对于模板,ODR有例外,即编译器会将实例化的函数模板标记为这样,并且在链接过程中将删除除一个之外的所有模板。我注意到编译器实际上并不关心在不同翻译单元中生成的此类实例化代码是否实际上是相同的或至少是等效的。

在上面的代码中,就是这种情况。编译,链接和运行

c++ a.cpp b.cpp -o result -std=c++17 && ./result

它将产生结果

foobar called from b.cpp: 1
foobar called from a.cpp: 1

显然,对象文件b.o中的实例化被抛弃,转而支持a.o中的那个。编译和链接时与b.cpp和a.cpp交换,如

c++ b.cpp a.cpp -o result -std=c++17 && ./result

结果将是

foobar called from b.cpp: 2
foobar called from a.cpp: 2

所以恰恰相反:在待链接目标文件列表中首先提到的实例化将是幸存的实例。这种行为是否定义在标准范围内?根据构建系统,提到目标文件的顺序可能相当随意,但是,如在这样的示例中,它会导致非常不同的程序和可能的繁琐错误。即使我尝试通过添加

来明确地从a.cpp实例化该版本
template double foobar<double>(double);

它不会使foobar&lt;&gt;当在链接器列表中的a.o之前提到b.o时,来自a.cpp的模板存活。

1 个答案:

答案 0 :(得分:3)

  

我知道对于模板,ODR有例外

模板的ODR没有例外,模板功能只是inline

您的程序违反了ODR 您会遇到与常规内联函数类似的问题。