Why is this C++ explicit template specialization code illegal?
Asked Answered
O

3

7

(Note: I know how it is illegal, I'm looking for the reason that the language make it so.)

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

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

template<> void Foo<int>();

Error:

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

A quick pass with Google found this citation of the spec but that offers only the what and not the why.

Edit:

Several responses have forwarded the argument (e.g. confirmed my speculation) that the rule is this way because to do otherwise would violate the One Definition Rule (ODR). However, this is a very weak argument because it doesn't hold, in this case, for two reaons:

  1. Moving the explicit specialization to another translation unit solves the problem and doesn't seem to violate the ODR (or so the linker says).
  2. The short form of the ODR (as applied to functions) is that you can't have more than one body for any given function, and I don't. The only place the body of the function is ever defined is in the explicit specialization, so the call to Foo<int> can't define a generic specialization of the template because there is no generic body to be specialized.

Speculation on the matter:

A guess as to why the rule exist at all: if the first line offered a definition (as opposed to a declaration), an explicit specialization after an instantiation would be a problem because you would get multiple definitions. But in this case, the only definition in sight is the explicit specialization.

Oddly the following (or something like it in the real code I'm working on) works:

File A:

template<class c> void Foo();

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

File B:

template<class c> void Foo();

template<> void Foo<int>();

But to use that in general starts to create a spaghetti imports structure.

Oakleil answered 3/5, 2011 at 17:33 Comment(1)
"Oddly the following works" ... ill-formed; no diagnostic required.Ponce
J
5

A guess as to why the rule exist at all: if the first line offered a definition (as opposed to a declaration), an explicit specialization after an instantiation would be a problem because you would get multiple definitions. But in this case, the only definition in sight is the explicit specialization.

But you do have multiple definitions. You have already defined Foo< int > when you instantiate it and after that you try to specialize the template function for int, which is already defined.

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

template<> void Foo<int>(); // Trying to specialize already defined Foo<int>
Jeweljeweler answered 3/5, 2011 at 17:46 Comment(2)
While that may be technically correct it doesn't asnwer my question of why. -- How can Foo<inT>() define anything? The compiler has no idea what the body of the function is going to be, so the best it can do is figure out what the (mangled) name of the function is and inject a call that the linker will patch up. Furthermore, if that did define Foo<in> here, then even moving the explicit specialization into another file shouldn't fix the problem as you would still have a violation of the one definition rule.Oakleil
How big should the object be to allocate? Explicit specializations can change the object entirely, so it could be bigger or smaller. No way that your main() will be adjusted to compensate for that.Lobar
C
2

This code is illegal because explicit specialization appears after the instantiation. Basically, the compiler first saw the generic template, then it saw its instantiation and specialized that generic template with a specific type. After that it saw a specific implementation of the generic template that was already instantiated. So what is the compiler supposed to do? Go back and re-compile the code? That's why it is not allowed to do that.

Candiot answered 3/5, 2011 at 17:52 Comment(1)
How could the compiler specialized that generic template with a specific type? There is no function body to work with. The only thing it has at that point is a prototype. (see comment to Magnus Skog)Oakleil
W
2

You have to think of an explicit specialization as a function declaration. Just like if you had two overloaded functions (non-templated), if only one declaration can be found before you try to make a call to the second version, the compiler is going to say that it cannot find the required overloaded version. The difference with templates is that the compiler can generate that specialization based on the general function template. So, why is it forbidden to do this? Because the full template specialization violates the ODR when it is seen, since, by that time, there already exists a template specialization for the same type. When a template is instantiated (implicitly or not), the corresponding template specialization is also created, such that later use (in the same translation unit) of the same specialization will be able to reuse the instantiation and not duplicate the template code for every instantiations. Obviously, the ODR applies just as well to template specializations as it applies elsewhere.

So, when the quoted text says "no diagnostic is required", it just means that the compiler is not required to provide you with the insightful remark that the problem is due to an instantiation of the template occurring sometime before the explicit specialization. But, if it doesn't do that, the other option is to give the standard ODR violation error, i.e., "multiple definitions of 'Foo' specialization for [T = int]" or something like that, which wouldn't be as helpful as the more clever diagnostic.

RESPONSE TO EDIT

1) Although the saying goes that all template function definitions (i.e. implementation) must be visible at the point of instantiation (such that the compiler can substitute the template argument(s) and instantiate the function template). However, implicit instantiation of the function template only requires that the declaration of the function be available. So, in your case, splitting it into two translation units works, because it does not violate ODR (since in that TU, there is only one declaration of Foo<int>), the declaration if Foo<int> is available at the implicit instantiation point (through Foo<T>), and the definition of Foo<int> is available to the linker within TU B. So, no one has argued that this second example is "not supposed to work", it works as it is supposed to. Your question is about a rule that applies within one translation unit, don't counter the arguments by saying that the error doesn't occur when you split it into two TUs (especially when it clearly should work fine in two TUs, according to the rules).

2) In your first example, either there will be an error because the compiler cannot find the general function template (the non-specialized implementation) and thus cannot instantiate Foo<int> from the general template. Or, the compiler will find a definition for the general template, use it to instantiate Foo<int>, and then throw an error because a second template specialization Foo<int> is encountered. You seem to think that the compiler will find your specialization before it gets to it, it doesn't. C++ compilers compile the code from top to bottom, they don't go back and forth to substitute stuff here and there. When the compiler gets to the first use of Foo<int>, it sees only the general template at that point, assumes there will be an implementation of that general template that can be used to instantiate Foo<int>, it is not expecting a specialized implementation for Foo<int>, it is expecting and will use the general one. Then, it sees the specialization and throws the error, because it already had made its mind that the general version was to be used, so, it does see two distinct definitions for the same function, and yes, it does violate ODR. It's as simple as that.

WHY OH WHY!!!

The 2 TU case has to work because you should be able to share template instantiations between TUs, that's a feature of C++, and a useful one (in case when you have a small number of possible instantiations, you can pre-compile them).

The 1 TU case cannot be allowed because declaring something in C++ tells the compiler "there is this thing defined somewhere". You tell the compiler "there is a general definition of the template somewhere", then say "I want to use the general definition to make the function Foo<int>", and finally, you say "whenever Foo<int> is called, it should use this special definition". That's a flat out contradiction! That's why the ODR exists and applies to this context, to forbid such contradictions. It doesn't matter whether the general definition "to-be-found" is not present, the compiler expects it, and it has to assume that it does exist and that it is different from the specialization. It cannot go on and say "ok, so, I'll look everywhere else in the code for the general definition, and if I cannot find it, then I will come back and 'approve' this specialization to be used instead of the general definition, but if I find it I will flag this specialization as an error". Nor can it go on and flatly ignore the desire of the programmer by changing code that clear shows intent to use the general template (since the specialization is not declared yet), for code that uses a specialization that appears later. I can't possibly explain the "why" any more clearly than that.

The 2 TU case is completely different. When the compiler is compiling TU A (that uses Foo<int>), it will look for the general definition, fail to find it, assume that it will be linked-in later as Foo<int>, and leaves a symbol placeholder. Then, since the linker will not look for templates (templates are NOT exportable, in practice), it will look for a function that implements Foo<int>, and it doesn't care whether it is a specialized version or not. The linker is happy as long as it finds the same symbol to link to. This is so, because it would be a nightmare to do it otherwise (look up discussions on "exported templates") for both the programmers (not being able to easily change functions in their compiled libraries) and for the compiler vendors (having to implement this linking crazy scheme).

Wagstaff answered 3/5, 2011 at 18:3 Comment(4)
I know that code is compiled from top to bottom, that is in fact the crux of the issue: because both cases are identical up to the end of main, the compiler will do exactly the same thing in both cases: if it decides that Foo<int>() is to use the general template in the 1 TU case, than it will make the same decision in the 2 TU case. Why should moving the explicit specialization into another TU make any difference? (In fact I have reason to believe that it doesn't and both cases are illegal.) -- But all this is beside the point, my question is why-is (not what-of) the 1 TU case is illegal?Oakleil
My last edit is the "why", no doubts about that. If you are not satisfied by that explanation, I don't know what will. You have to understand that behind many of the rules of C++, there were careful debates, studies and many were devised from a very pragmatic point-of-view. There is always a weighting between "is it useful enough" and "is allowing it too much trouble or danger for the robustness of the language". I think your example weights poorly on the former and weights heavily on the latter.Wagstaff
[On if the 2TU case is legal] If the 1TU case tells the compiler "there is a general definition of the template somewhere" and then it assumes the general one for the call, then the 2TU case does as well, because the code, up through the end of main (past the call), is bit-for-bit identical in both cases. From there, for the 1TU case to be illegal it has to refuse to allow the function call to (re?)bind to a given symbol that is in the same TU and for the 2TU case to be legal it must allow the function call to (re?)bind to a given symbol that is in another TU. That doesn't sound right.Oakleil
BTW: at this point, there is something like a dozen people on the planet who's word I would trust on this question.Oakleil

© 2022 - 2024 — McMap. All rights reserved.