C++关于模板等价/重新定义的规则

C++ rules about template equivalence/redefinition?

本文关键字:定义 规则 新定义 于模板 C++      更新时间:2023-10-16

以下C++翻译单元由于以下三行而不正确:

template <int x, int y> struct S {}; // [1]
template <int w, int z> struct S {}; // [2] <-- ill-formed
template <int x> struct S<x,5+2> {}; // [3]
template <int w> struct S<w,3+4> {}; // [4] <-- ill-formed
template <> struct S<6+1,4+3> {}; // [5] 
template <> struct S<2+5,8-1> {}; // [6] <-- ill-formed

问题是[2]是[1]的重新定义,[4]是[3]的重新定义和[6]是[5]的重新定义。

C++标准中的哪种特定语言导致了这种格式错误?

当两个模板定义定义了一个模板的相同专业化时(当它们是一个模板中两个不同的专业化时),它在哪里声明?

哪里说这是不允许的(重新定义相同的专业化)?

我要冒险说,"一个定义规则"适用于这里(重点是他们和我的):

任何翻译单元都不得包含任何变量、函数、类类型、枚举类型或模板的多个定义。

请注意,模板也属于此规则,而不仅仅是变量。


至于编译器为什么允许这样做,你不是在重新定义模板,而是在专门化它。这与大多数专门化没有太大区别。这里奇怪的区别在于,您的专业化是模板化的。

这可以通过在1:之前先执行3来看出

template <int x> struct S {}; // [3] <-- S takes one parameter now
template <int x, int y> struct S {}; // [1] <-- this is a redefinition, because S only takes one parameter.

如果你问我,这是模板化专业化(接受模板的专业化)和重新定义之间的一条细线,但从技术上讲,你的例子是前者,所以这就是它工作的原因。


旁注:如果你愿意,你可以专门化vardic模板来达到同样的效果:

template <typename... T>
struct Foo;
template <typename T1>
struct Foo<T1> {};
template <typename T1, typename T2>
struct Foo<T1,T2> {};

同样,这让我觉得这种类型的重新定义和接受模板的专业化之间的界限真的很武断,但这只是我的观点。

template <int x, int y> struct S {}; // [1]
template <int w, int z> struct S {}; // [2] <-- ill-formed

这是标准的、通用的C++所禁止的。

名称表示实体(或标签,但我们忽略它)。名字来源于声明。定义是声明的子集,因此它也引入了名称。

一条定义规则说:

任何翻译单元都不得包含任何变量、函数、类类型、枚举类型或模板的多个定义。

在语句[1]中,将创建一个模板实体。该实体由名称CCD_ 2表示。

在语句[2]中,创建了一个模板实体。该实体由名称S表示。

这两个名字是一样的。如果一个名称表示一个实体(如以前建立的),那么在适当的范围内的相同名称表示相同的实体。因此,两个语句都声明了相同的实体。

这是ODR禁止的。


template <> struct S<6+1,4+3> {}; // [5] 
template <> struct S<2+5,8-1> {}; // [6] <-- ill-formed

从语法上讲,S<6+1,4+3>是一个简单模板id。现在,有人可能会认为这使得它成为一个类模板部分专业化定义。但它没有,因为模板参数是空的,这使得这是一个显式的专门化。即便如此,S<6+1,4+3>仍然是这个定义的名称。明确的专业化定义仍然是定义。

那么问题来了,S<6+1,4+3>S<2+5,8-1>是同一个名字吗?

以下是关于我们如何区分相同名称的规则摘录:

它们是模板id,引用相同的类、函数或变量([temp.type])

简单模板id在语法上是模板id的子集。那么,这两个简单模板id是否"引用了相同的类、函数或变量"?

这是由一套复杂的规则决定的。特别注意:

它们对应的整型或枚举类型的非类型模板参数具有相同的值

6+12+5是相同的值。CCD_ 11和CCD_。因此,这两个简单模板id是相同的名称,因此是相同的实体。由于这两个定义定义的是同一个实体,这违反了ODR,如前所述。


部分模板专门化是另一回事。

template <int x> struct S<x,5+2> {}; // [3]
template <int w> struct S<w,3+4> {}; // [4] <-- ill-formed

前面围绕simple-template-id的逻辑工作。。。到了一定程度。部分专业化使用简单模板ids作为名称,因此它们引入的实体使用相同的规则来测试等价性。

然而,这些规则实际上并没有说明这里会发生什么。CCD_ 14和CCD_;它们是模板参数。只有当选择了他们的专业时,才能知道他们的实际价值。其他等价规则实际上并没有讨论当模板参数是模板参数时会发生什么。

所以从技术上来说,这似乎不是同一个定义。然而

根据如何选择模板专门化的规则,语句[3][4]引入了完全不明确的部分专门化。请看,在专门化之间进行选择的规则涉及一系列复杂的事情,包括将部分专门化转换为一系列函数声明,以及使用模板函数重载规则进行排序。所以我没有引用这句话。

这两种类型的归结方式是,您试图实例化的任何S<value>都是不明确的。无法从S0中检测到[3],因此任何使用两者的尝试都将是错误的。

所以你的编译器基本上是抢先一步,意识到你不可能使用任何一种专门化,然后告诉你的代码坏了。

我可能在规范中遗漏了一些内容,实际上这些名称是相同的,因为它们显然是相同的名称。但我没有找到。