模板定义和 ODR 冲突

Jod*_*cus 3 c++ linker templates one-definition-rule language-lawyer

想象一下我有两个不同的翻译单元 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';
}
Run Code Online (Sandbox Code Playgroud)

和b.cpp:

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

double bar() {
    return foobar(1.);
}
Run Code Online (Sandbox Code Playgroud)

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

上面的代码就是这种情况。编译、链接和运行时

c++ a.cpp b.cpp -o result -std=c++17 && ./result
Run Code Online (Sandbox Code Playgroud)

它将产生结果

foobar called from b.cpp: 1
foobar called from a.cpp: 1
Run Code Online (Sandbox Code Playgroud)

显然,对象文件 bo 内的实例化被抛弃,取而代之的是 ao 中的实例化当编译并链接 b.cpp 和 a.cpp 交换时,就像

c++ b.cpp a.cpp -o result -std=c++17 && ./result
Run Code Online (Sandbox Code Playgroud)

结果将是

foobar called from b.cpp: 2
foobar called from a.cpp: 2
Run Code Online (Sandbox Code Playgroud)

因此,会发生完全相反的情况:要链接的对象文件列表中首先提到的实例化将是幸存的实例化。标准中是否定义了这种行为?根据构建系统,提及目标文件的顺序可以相当任意,但是,正如在这样的示例中,它会导致非常不同的程序,并且可能会出现麻烦的错误。即使我尝试通过添加来显式实例化 a.cpp 中的版本

template double foobar<double>(double);
Run Code Online (Sandbox Code Playgroud)

当链接器列表中 ao 之前提到 bo 时,它不会使 a.cpp 中的 foobar<> 模板存活。

Jar*_*d42 5

我知道对于模板,ODR 存在例外情况

模板的 ODR 没有例外,只是模板函数是inline

并且您的程序存在 ODR 违规。
对于常规内联函数,您也会遇到类似的问题。