7

(注意:我知道它是非法的,我正在寻找语言使其如此的原因。)

template<class c> void Foo();  // Note: no generic version, here or anywhere.

int main(){
  Foo<int>();
  return 0;
}

template<> void Foo<int>();

错误:

error: explicit specialization of 'Foo<int>' after instantiation

与谷歌的快速通过发现了这个规范的引用,但它只提供了什么而不是为什么。

编辑:

一些回应转发了这样的论点(例如证实了我的推测),即规则是这样的,因为否则会违反单一定义规则(ODR)。然而,这是一个非常弱的论点,因为在这种情况下,它不成立,原因有两个:

  1. 将显式专业化移动到另一个翻译单元可以解决问题,并且似乎不会违反 ODR(或者链接器说)。
  2. ODR 的简短形式(应用于函数)是任何给定函数都不能有多个主体,而我没有。唯一定义函数体的地方是显式特化,因此调用Foo<int>不能定义模板的泛型特化,因为没有要特化的泛型体。

对此事的猜测:

关于为什么存在该规则的猜测:如果第一行提供了定义(而不是声明),那么实例化之后的显式特化将是一个问题,因为您将获得多个定义。但在这种情况下,唯一的定义是显式专业化。

奇怪的是以下(或我正在处理的真实代码中的类似内容)有效:

档案一:

template<class c> void Foo();

int main(){
  Foo<int>();
  return 0;
}

文件 B:

template<class c> void Foo();

template<> void Foo<int>();

但是使用它通常会开始创建意大利面条导入结构。

4

3 回答 3

5

关于为什么存在该规则的猜测:如果第一行提供了定义(而不是声明),那么在实例化之后显式特化将是一个问题,因为您将获得多个定义。但在这种情况下,唯一的定义是显式专业化。

但是您确实有多个定义。当您实例化 Foo< int > 时,您已经定义了它,然后您尝试将模板函数专门化为已定义的 int。

int main(){
  Foo<int>();    // Define Foo<int>();
  return 0;
}

template<> void Foo<int>(); // Trying to specialize already defined Foo<int>
于 2011-05-03T17:46:14.263 回答
2

此代码是非法的,因为显式特化出现在实例化之后。基本上,编译器首先看到了泛型模板,然后它看到了它的实例化并用特定类型专门化了该泛型模板。之后,它看到了已经实例化的通用模板的特定实现。那么编译器应该做什么呢?回去重新编译代码?这就是为什么不允许这样做。

于 2011-05-03T17:52:31.810 回答
2

您必须将显式特化视为函数声明。就像您有两个重载函数(非模板化)一样,如果在尝试调用第二个版本之前只能找到一个声明,编译器会说它找不到所需的重载版本。与模板的不同之处在于编译器可以根据通用函数模板生成该特化。那么,为什么禁止这样做呢?因为完整的模板特化违反了 ODR,因为到那时,已经存在相同类型的模板特化。当模板被实例化(隐式或非隐式)时,也会创建相应的模板特化,这样以后使用(在同一个翻译单元中)相同的专业化将能够重用实例化并且不会为每个实例化重复模板代码。显然,ODR 也适用于模板专业化,就像它适用于其他地方一样。

因此,当引用的文本说“不需要诊断”时,它只是意味着编译器不需要为您提供有见地的评论,即问题是由于模板的实例化发生在显式专业化之前的某个时间。但是,如果它不这样做,另一种选择是给出标准的 ODR 违规错误,即“[T = int] 的 'Foo' 专业化的多个定义”或类似的东西,这不会是作为更聪明的诊断很有帮助。

对编辑的回应

1)虽然俗话说所有模板函数定义(即实现)必须在实例化点可见(这样编译器可以替换模板参数并实例化函数模板)。但是,函数模板的隐式实例化只需要函数的声明可用。因此,在您的情况下,将其分成两个翻译单元是可行的,因为它不违反 ODR(因为在该 TU 中,只有一个 的声明Foo<int>),声明 ifFoo<int>在隐式实例化点(通过Foo<T>)可用,并且的定义Foo<int>TU B 中的链接器可以使用。因此,没有人认为第二个示例“不应该工作”,它按预期工作。根据规则)。

2)在您的第一个示例中,要么会出现错误,因为编译器找不到通用函数模板(非专业实现),因此无法Foo<int>从通用模板实例化。或者,编译器会找到通用模板的定义,使用它来实例化Foo<int>,然后因为遇到第二个模板特化而抛出错误Foo<int>。您似乎认为编译器会在找到您的专业之前找到它,但事实并非如此。C++ 编译器从上到下编译代码,它们不会来回替换这里和那里的东西。当编译器第一次使用 时Foo<int>,它此时只看到通用模板,并假设该通用模板的实现可用于实例化Foo<int>,它不期望 的专门实现Foo<int>,它期望并将使用通用实现。然后,它看到特化并抛出错误,因为它已经确定要使用通用版本,所以它确实看到了同一函数的两个不同定义,是的,它确实违反了 ODR。就这么简单。

为什么哦为什么!!!

2 TU 案例必须工作,因为您应该能够在 TU 之间共享模板实例化,这是 C++ 的一个特性,也是一个有用的特性(如果您有少量可能的实例化,您可以预编译它们)。

不能允许 1 TU 的情况,因为在 C++ 中声明某些东西会告诉编译器“在某处定义了这个东西”。你告诉编译器“某处有模板的通用定义”,然后说“我想使用通用定义来制作函数Foo<int>”,最后,你说“无论何时Foo<int>调用,它都应该使用这个特殊定义”。这是一个彻头彻尾的矛盾!这就是为什么ODR 存在并适用于这种情况,以禁止此类矛盾。通用定义“to-be-found”是否不存在并不重要,编译器期望它,它必须假设它确实存在并且它与专业化不同。它不能继续说“好的,所以,我会在代码中的其他地方寻找通用定义,如果找不到,那么我会回来并‘批准’这个专业化来代替通用定义定义,但如果我找到它,我会将此专业化标记为错误”。对于使用稍后出现的特化的代码,它也不能继续通过更改明确显示使用通用模板的意图的代码(因为尚未声明特化)来完全忽略程序员的愿望。我可以'

2 TU 的情况完全不同。当编译器在编译 TU A(使用Foo<int>)时,它会寻找通用定义,找不到它,假设它稍后会被链接为Foo<int>,并留下一个符号占位符。然后,由于链接器不会查找模板(实际上模板不可导出),它会查找实现 的函数Foo<int>,并且它不关心它是否是专用版本。链接器只要找到要链接的相同符号就很高兴。之所以如此,是因为对于程序员(无法轻松更改其编译库中的函数)和编译器供应商(必须实现这个链接疯狂的计划)。

于 2011-05-03T18:03:15.273 回答