【问题标题】:Is initialization of a reference variable primary template required even when it is never instantiated?即使从未实例化引用变量主模板,是否也需要对其进行初始化?
【发布时间】:2019-04-30 18:41:15
【问题描述】:

在 C++14 中声明一个引用模板而不初始化主引用模板是否合法,只要它从未实例化?

template<class T>
const T& ref;

template<>
auto ref<int> = 1;

auto x = ref<int>;

这会在 GCC 和 Clang 上产生不同的结果:

$ g++ -std=c++14 -c ref.cpp
$

$ clang -std=c++14 -c ref.cpp
ref.cpp:2:10: error: declaration of reference variable 'ref' requires an
      initializer
const T& ref;
         ^~~
1 error generated.

必须初始化主引用模板是没有意义的,因为在它被实例化之前,它是一个模板,而不是一个引用。

我发现我可以这样做:

template<class T>
const T& ref = "Meaningless initialization with any value of any type";

template<>
auto ref<int> = 1;

auto x = ref<int>;

因为显然 GCC 和 Clang 都接受但忽略引用模板初始化程序 RHS,只要它是一个有效的表达式并且主引用模板永远不会被实例化。并且任何类型的任何表达式都满足 Clang 的初始化要求。

只要主引用模板从未实例化,GCC 就不需要初始化器。这似乎是“精神上”的正确行为,因为在实际实例化引用模板之前,它不应该需要初始化程序。

标准在参考模板上并不是 100% 明确的。以下是我在变量模板实例化方面能找到的一点点:

14.7.1

除非变量模板特化已被显式实例化或显式特化,否则变量模板特化会在使用特化时隐式实例化。

...

实现不应隐式实例化...一个不需要实例化的变量模板...。

14.7.2

除了内联函数、类型从其初始值设定项或返回值 (7.1.6.4)、const 文字类型变量、引用类型变量和类模板特化、显式的声明实例化声明具有抑制它们引用的实体的隐式实例化的效果。 [注意:目的是作为显式实例化声明主题的内联函数在使用odr(3.2)时仍将被隐式实例化,以便可以考虑将主体内联,但没有外联副本内联函数的一部分将在翻译单元中生成。—尾注]

14.7.3

显式特化的函数模板、类模板或变量模板的声明应在显式特化的声明之前。 [ 注意:需要声明,而不是模板的定义。 ——尾注 ]。

编辑添加:

变量模板声明、类模板声明或函数模板声明分别不同于变量声明、类声明或函数声明,并且不受相同规则的约束。在模板被实例化之前,它只是一个模板。

类模板、变量模板和函数模板可以在不提供主要定义的情况下声明,仅提供专门化定义。以下代码在 Clang 和 GCC 上都是合法的:

// Class
template<class T> class foo;        // Declaration, not definition
template<> class foo<int> {};       // Specialization definition
using ifoo = foo<int>;              // Specialization instantiation

// Function
template<class T> void bar(T);      // Declaration, not definition
template<> void bar(int) {}         // Specialization definition
void (*barp)(int) = bar<int>;       // Specialization instantiation

// Variable
int j;
template<class T> T* point;         // Declaration, not definition
template<> int* point<int> = &j;    // Specialization definition
int *k = point<int>;                // Specialization instantiation

那么,问题是为什么参考模板应该有所不同?为什么引用模板的主要声明必须是具有引用初始化的定义,而其他任何模板都不是这样?

template<class T> const T& ref;      // Declaration, not definition
template<> const int& ref<int> = 1;  // Specialization definition
const int& iref = ref<int>;          // Specialization instantiation

【问题讨论】:

  • 一个变量的声明就是一个定义,除非它是用extern关键字声明的。如果你的意思是声明,你可以表达它:template &lt;class T&gt; extern const T&amp; ref;。 extern 不会改变链接。
  • 这是一个变量模板的声明,而不是一个变量。
  • 评论可能更迂腐、更笼统、更难理解:basic.def

标签: c++ templates language-lawyer clang++ variable-templates


【解决方案1】:

我相信 [temp.res]/8 涵盖了这一点:

...程序格式错误,不需要诊断,如果:

  • 无法为模板或模板中的 constexpr if 语句的子语句生成有效的特化,并且模板未实例化...

您编写的参考模板永远不会产生有效的特化,因为实例化产生的变量总是需要一个初始化器。


我提供的引用来自C++17,但C++14中有类似的说法。

【讨论】:

  • 我不相信这条规则适用于此。首先,14.6 正在讨论模板定义内部的名称解析,我们在这里不讨论——const T&amp; 不引用模板定义之外的任何名称。其次,ref&lt;int&gt; 在唯一引用ref 模板的地方生成一个有效的特化。 ref&lt;int&gt; 特化被实例化。程序中没有对 ref 模板的引用,在查找时无法实例化该模板。这就是为什么 GCC 不关心是否定义了主模板,因为它永远不需要被实例化。
  • @leek 如果我们定义一个函数模板并且const T&amp; ref; 出现在正文中怎么办?你能明白为什么编译器想要拒绝它吗?您的问题中的情况没有什么不同。由于引用需要初始化,这种说法可以被视为语法上不正确。
  • 正如我告诉@Oliv 的,这是一个变量模板声明,而不是变量声明。打个比方,如果您将template&lt;class T&gt; void foo(T); 声明为函数模板声明(不是函数声明),然后专门化template&lt;&gt; void foo(int x) { },则无需定义主函数模板即可实例化和调用foo&lt;int&gt;(); .模板不是对象/函数/类型,因此它们的声明/定义规则与对象、函数或类型不同。在它们被隐式或显式实例化之前,它们只是模板。
  • template&lt;class T&gt; void foo(T) { const T&amp;ref; } 在 GCC 上编译得很好,但在 Clang 上却不行,原因可能与 OQ 相同。这是一个函数模板定义,而不是函数定义。在实例化之前,const T&amp; ref 在没有初始化程序的情况下出现的事实是无关紧要的,根据标准的一种解释(GCC 似乎使用)。在引用模板的情况下,它变得比这个例子更重要,因为我们可能想要没有默认定义的特化,这对于类模板、函数模板和非引用变量模板是合法的。
  • @leek 不,你误解了标准,它说“不能为模板生成有效的专业化”。这意味着没有任何特化通过实例化模板产生是有效的。显式特化不算在内。
猜你喜欢
  • 2013-07-04
  • 1970-01-01
  • 1970-01-01
  • 2017-11-10
  • 1970-01-01
  • 2014-11-01
  • 1970-01-01
  • 2017-07-06
  • 1970-01-01
相关资源
最近更新 更多