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

时间:2018-10-05 11:03:01

标签: c++ one-definition-rule explicit-instantiation

这个问题是在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有助于将我指向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的执行是为了避免此类问题。

2 个答案:

答案 0 :(得分:2)

尤里卡!我终于找到了相关段落[temp.spec] / 5

  

对于给定的模板和给定的模板参数集,

     
      
  • (5.1)   一个显式的实例化定义最多应在程序中出现一次,

  •   
  • (5.2)   如[basic.def.odr]和

  • 中所指定,在程序中最多只能定义一个显式专业化   
  • (5.3)   除非显式实例化在显式专业化的声明之后,否则显式实例化和显式专业化的声明都不应出现在程序中。
  •   
     

不需要执行即可诊断是否违反此规则。

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

答案 1 :(得分:0)

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

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

  

对于给定的模板和给定的模板参数集,

     
      
  • (5.1)一个显式实例化定义最多应在程序中出现一次,

  •   
  • (5.2)在程序中(根据6.2)最多应定义一次显式专业化,[...]

  •   

功能模板在定义它们的相同翻译单元和其他翻译单元中可能具有不同的实例化点,当这些专业化的含义是在所有实例化方面都是相同的。

[temp.point]/6以来

  

显式实例化定义是由显式实例化指定的一个或多个专门化的实例化点。

[temp.point]/8

  

[...]如果根据一定义规则(6.2),两个不同的实例化点赋予模板专业化不同的含义,则程序格式错误,无需诊断。

第二种情况不违反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; }