1

The following C++ translation unit is ill-formed due to three lines:

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

The problem is that [2] is a redefinition of [1], [4] a redefinition of [3], and [6] a redefinition of [5].

Which specific language in the C++ standard is in play to make this ill-formed?

Where does it state when two template definitions are defining the same specialization of the a template (and when they are two different specializations of a template)?

Where does it say this isn't allowed (to redefine the same specialization)?

Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • 2
    Um, isn't this just standard C++? I mean, you can't have `void f() {}` and `void f() {}` in the same translation unit either. You can't redefine the same thing in a translation unit. I don't see how the template nature of any of this changes that. – Nicol Bolas Oct 04 '19 at 01:41
  • @NicolBolas: Yes, this is standard C++. I know it's ill-formed. I'm asking which specific rules from the C++ standard apply to make this translation unit ill-formed. An answer would include one or more references to the standard text. – Andrew Tomazos Oct 04 '19 at 01:47

2 Answers2

2

I'm going to go out on a limb here and say that the "one definition rule" applies here (emphasis theirs and mine):

No translation unit shall contain more than one definition of any variable, function, class type, enumeration type or template.

Notice that templates fall under this rule too, not just variables.


As to why the compiler allows this, you are not redefining the template, you are specializing it. It's not really a lot different than most specializations. The weird difference here is that your specialization is templated.

This can be seen by executing 3 first, before 1:

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.

If you ask me, it's a fine line between templated specialization (a specialization that accepts a template) and redefinition, but your example is technically the former, so that's why it works.


Side note: If you want, you can always specialize a vardic template to achieve the same effect:

template <typename... T>
struct Foo;

template <typename T1>
struct Foo<T1> {};

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

Again, this to me makes the line between this type of redefinition and specialization that accepts a template feel really arbitrary, but that would merely just be my opinion.

  • Why are [1] and [3] and [5] allowed? Aren't they specializations of the same template? Or are they three different templates? If the later, why are [1] and [2] the same template where as [1] and [3] are not? – Andrew Tomazos Oct 04 '19 at 01:54
  • @AndrewTomazos: Are you asking how the concept of "same template" is defined? Or what it means to have a "definition" of a template? – Nicol Bolas Oct 04 '19 at 01:58
  • @AndrewTomazos Perhaps because, in a sense, you are in theory [specializing a vardic template](https://stackoverflow.com/a/11969168/10957435) when you are doing this? –  Oct 04 '19 at 02:04
  • Looking at your code more closely, this is allowed because you are specializing `S` each time, so to speak, with [1],[3],and [5]. [2],[4], and [6] are redefinitions of those specializations. –  Oct 04 '19 at 02:12
  • The line between specialization and redefinition surely isn't "arbitrary", is it? If you would imagine a 2D grid, with `int`s on the two axes, then both [1] and [2] cover the entire grid, both [3] and [4] cover the same line, and both [5] and [6] cover the same point. ODR is only violated when the area covered is identical, otherwise it's just specialization. If one specialization is strictly more specific/is contained in another, it is preferred when the template is resolved, otherwise you may get an ambiguity. (The difference between initial definition and specialization is in the syntax.) – HTNW Oct 04 '19 at 03:31
  • @HTNW Certainly not, especially from a compiler-writing side. What I'm saying is that in practicality, there isn't really a noticeable difference when you can accomplish basically the same thing with "specializing a vardic template". There are lots of differences, of course, but in a practical use sense it seems to me like there is not much of a difference between specializing a vardic template, and being able to redefine a template. Other than, of course, the fact that C++ explicitly does not allow the former. –  Oct 04 '19 at 04:15
1
template <int x, int y> struct S {}; // [1]
template <int w, int z> struct S {}; // [2] <-- ill-formed

This is forbidden by standard, common C++ stuff.

Names denote entities (or labels, but let's ignore that). Names come from declarations. A definition is a subset of a declaration, so it too introduces names.

The one-definition rule says:

No translation unit shall contain more than one definition of any variable, function, class type, enumeration type, or template.

In statement [1], a template entity is created. That entity is denoted by the name S.

In statement [2], a template entity is created. That entity is denoted by the name S.

These two names are the same. If a name denotes an entity (as previously established), then the same name within the appropriate scope denotes the same entity. Thus, both statements declare the same entity.

Which is forbidden by the ODR.


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

Grammatically speaking, S<6+1,4+3> is a simple-template-id. Now, one might think that this makes this into a class template partial specialization definition. But it doesn't, because the template parameter is empty, which makes this an explicit specialization. Even so, S<6+1,4+3> is still the name of this definition. And explicit specialization definitions are still definitions.

So the question is, is S<6+1,4+3> the same name as S<2+5,8-1>?

Here's an excerpt from the rules about how we tell what the same name is:

they are template-ids that refer to the same class, function, or variable ([temp.type])

A simple-template-id is grammatically a subset of template-id. So do those two simple-template-ids "refer to the same class, function, or variable"?

That is determined by a complex set of rules. In particular, note:

their corresponding non-type template arguments of integral or enumeration type have identical values

6+1 and 2+5 are the same value. 4+3 and 8-1 are the same value. Therefore, those two simple-template-ids are the same name and therefore the same entity. And since these two definitions are defining the same entity, that violates ODR, as previously indicated.


Partial template specialization is another matter.

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

The preceding logic surrounding simple-template-id works... to a point. Partial specializations use simple-template-ids for their names, so the entities they introduce use the same rules to test equivalence.

However, those rules don't actually say what happens here. x and w aren't values; they are template parameters. Their actual value can only be known when their specializations are chosen. And the other equivalence rules don't actually talk about what happens when a template argument is a template parameter.

So technically, it would seem that these are not the same definition. However...

In accord with the rules of how a template specialization is chosen, the statements [3] and [4] introduces partial specializations that are entirely ambiguous. See, the rules for selecting between specializations involves a complex set of things, including a conversion of your partial specializations into a series of function declarations and using template function overloading rules to sort it out. So I'm not quoting that.

The way it boils down for your two types is that any S<value> that you try to instantiate will be ambiguous. There's no way to detect [3] from [4], so any attempt to use either will be ill-formed.

So your compiler basically jumps the gun, realizing that you cannot possibly use either specialization, and tells you that your code is broken.

There may be something I've missed somewhere in the spec that actually says that the names are the same, since they obviously are the same name. But I haven't found it.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982