为什么显式模板实例化不会破坏 ODR?

Why does explicit template instantiation not break ODR?

这个问题出现在 this answer 的上下文中。

如我所料,此翻译单元无法编译:

template <int Num> int getNum() { return Num; }
template int getNum<0>();
template int getNum<0>();  // error: duplicate explicit instantiation of 'getNum<0>'
int main() { getNum<0>(); return 0; }

我明白这一点,我已经尝试两次进行相同的显式模板实例化。然而,事实证明,将其分成不同的单元,它编译:

// decl.h
template <int Num> int getNum() { return Num; }

// a.cc
#include <decl.h>
template int getNum<0>();

// b.cc
#include <decl.h>
template int getNum<0>();
int main() { getNum<0>(); return 0; }

我没想到会这样。我假设具有相同参数的多个显式模板实例化会破坏 ODR,但情况似乎并非如此。然而,这确实失败了:

// decl.h
template <int Num> int getNum();

// a.cc
#include "decl.h"
template <> int getNum<0>() { return 0; }

// b.cc
#include "decl.h"
template <> int getNum<0>() { return 0; }
int main() { getNum<0>(); return 0; }

用户 Oliv helpfully pointed me to this relevant paragraph in the standard,但我仍然对此感到有些困惑,所以我希望有人能用更简单的术语解释这背后的逻辑(例如,应该或不应该考虑破坏 ODR 和为什么我的期望是错误的)。

编辑:

再举一个例子,这里有一个程序分为两个单元,它编译正确但产生的结果可能令人惊讶:

// a.cc
template <int Num> int getNum() { return Num + 1; }
template int getNum<0>();

// b.cc
#include <iostream>
template <int Num> int getNum() { return Num; }
template int getNum<0>();
int main() { std::cout << getNum<0>() << std::endl; return 0; }

输出:

1

在这种情况下,删除显式模板实例会产生 0。我知道有两个具有不同定义的模板不是常见的用例,但我认为 ODR 是为了避免此类问题而准确执行的。

尤里卡!我终于落在了相关段落上,[temp.spec]/5

For a given template and a given set of template-arguments,

  • (5.1) an explicit instantiation definition shall appear at most once in a program,

  • (5.2) an explicit specialization shall be defined at most once in a program, as specified in [basic.def.odr], and

  • (5.3) both an explicit instantiation and a declaration of an explicit specialization shall not appear in a program unless the explicit instantiation follows a declaration of the explicit specialization.

An implementation is not required to diagnose a violation of this rule.

因此显式模板实例化定义(而非隐式实例化)可能导致 ODR 违规,不需要诊断(至少 gcc 和 clang - ld 工具链不产生诊断)

显式特化和显式实例化定义都将违反 ODR,具体取决于它们的使用上下文和它们生成的实体的含义。

下面解释第一种和第三种情况,以及为什么它们确实违反了 NDR 的 ODR [temp.spec]/5

For a given template and a given set of template-arguments,

  • (5.1) an explicit instantiation definition shall appear at most once in a program,

  • (5.2) an explicit specialization shall be defined at most once in a program (according to 6.2), [...]

函数模板在定义它们的同一个翻译单元和其他翻译单元中可能有不同的实例化点,这些特化保证不会在含义时违反 ODR这些专业化的所有实例化点都是相同的。

自从 [temp.point]/6

An explicit instantiation definition is an instantiation point for the specialization or specializations specified by the explicit instantiation.

[temp.point]/8

[...] If two different points of instantiation give a template specialization different meanings according to the one-definition rule (6.2), the program is ill-formed, no diagnostic required.

第二种情况不违反ODR,因为这些TU中实例化的意思是一样的

// decl.h
template <int Num> int getNum() { return Num; }

// a.cc
#include <decl.h>
template int getNum<0>();

// b.cc
#include <decl.h>
template int getNum<0>();
int main() { getNum<0>(); return 0; }

但最后一个肯定不是有效的(违反ODR NDR),因为即使函数模板具有相同的签名,它们的实例化也会有不同的含义。你不能传递你得到的结果,标准不保证这些违规行为发生时的行为。

// a.cc
template <int Num> int getNum() { return Num + 1; }
template int getNum<0>();

// b.cc
#include <iostream>
template <int Num> int getNum() { return Num; }
template int getNum<0>();
int main() { std::cout << getNum<0>() << std::endl; return 0; }