Consider the following code snippet:
template <typename>
struct X { };
extern template struct X<int>;
int main()
{
X<int>{};
}
It compiles and links: live example on godbolt.org. I would expect it not to link due to the extern template
declaration.
My understanding is that extern template
means: "please don't instantiate this particular template specialization in this TU, it will be provided by some other TU and you can link against it".
The examples/descriptions. I've seen on isocpp and cppreference seem to validate my mental model. E.g.
From https://en.cppreference.com/w/cpp/language/class_template:
An explicit instantiation declaration (an extern template) skips implicit instantiation step: the code that would otherwise cause an implicit instantiation instead uses the explicit instantiation definition provided elsewhere (resulting in link errors if no such instantiation exists). This can be used to reduce compilation times by explicitly declaring a template instantiation in all but one of the source files using it, and explicitly defining it in the remaining file.
Why does my code snippet link? What is actually happening here?
EDIT - found this in the latest Standard draft:
If an entity is the subject of both an explicit instantiation declaration and an explicit instantiation definition in the same translation unit, the definition shall follow the declaration. An entity that is the subject of an explicit instantiation declaration and that is also used in a way that would otherwise cause an implicit instantiation in the translation unit shall be the subject of an explicit instantiation definition somewhere in the program; otherwise the program is ill-formed, no diagnostic required.
Does this mean that the code snippet I posted is ill-formed, NDR?
Why does my code snippet link? What is actually happening here?
Well, there's nothing to link. For one has to consider the effects of the explicit instantiation. From n3337:
[temp.explicit] (emphasis mine)
10 Except for inline functions and class template specializations, explicit instantiation declarations have the effect of suppressing the implicit instantiation of the entity to which they refer. [ Note: The intent is that an inline function that is the subject of an explicit instantiation declaration will still be implicitly instantiated when odr-used ([basic.def.odr]) so that the body can be considered for inlining, but that no out-of-line copy of the inline function would be generated in the translation unit. — end note ]
So the implicit instantiation of the class template specialization X<int>
, is not suppressed. It's also an aggregate, so its initialization occurs inline, and we get nothing to link against. However, if it had any members, those would be suppressed under paragraph 8:
An explicit instantiation that names a class template specialization is also an explicit instantiation of the same kind (declaration or definition) of each of its members (not including members inherited from base classes) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, except as described below.
So if you had instead of an aggregate something akin to this:
template <typename>
struct X {
X();
};
template <typename T>
X<T>::X() {}
extern template struct X<int>;
int main()
{
X<int>{};
}
That would fail as you expect, since it ODR uses a constructor whose definition is never instantiated. The declaration is instantiated, because the enclosing specialization is instantiated, as mentioned above. But we never get any definition, under the suppressing effect of the explicit instantiation declaration.