5

这个问题是在这个答案的背景下出现的。

正如我所料,这个翻译单元不能编译:

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帮助我指出了标准中的相关段落,但我仍然对此感到有些困惑,所以我希望有人可以用更简单的术语解释这背后的逻辑(例如,什么应该或不应该被认为是破坏 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 是为了避免此类问题而精确实施的。

4

2 回答 2

4

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

对于给定的模板和给定的一组模板参数,

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

  • (5.2) 一个显式特化在一个程序中最多只能定义一次,如[basic.def.odr]中所规定的,并且

  • (5.3) 显式实例化和显式特化声明都不得出现在程序中,除非显式实例化跟随显式特化声明。

诊断违反此规则的情况不需要实现。

所以显式模板实例化定义(不是隐式实例化)会导致 ODR 违规,不需要诊断(至少 gcc 和 clang - ld 工具链不会产生诊断)

于 2018-10-05T15:53:00.147 回答
0

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

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

对于给定的模板和给定的一组模板参数,

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

  • (5.2) 一个显式特化在程序中最多只能定义一次(根据 6.2),[...]

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

自从[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; }
于 2018-10-05T17:19:30.043 回答