[dcl.spec.auto]/14 states [emphasis mine]:
An explicit instantiation declaration does not cause the instantiation of an entity declared using a placeholder type, but it also does not prevent that entity from being instantiated as needed to determine its type. [ Example:
template <typename T> auto f(T t) { return t; } extern template auto f(int); // does not instantiate f<int> int (*p)(int) = f; // instantiates f<int> to determine its return type, but an explicit // instantiation definition is still required somewhere in the program
— end example ]
and [temp.explicit]/11 states [emphasis mine]:
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.
Now, consider the following program:
template <class T>
struct Foo {
static const auto& foo() { static T t; return t; }
};
// explicit instantiation declarations
extern template const auto& Foo<void>::foo();
extern template const auto& Foo<int>::foo();
int main() {}
This is well-formed; [temp.explicit]/11 does not apply as neither member function of class template specialization entities Foo<void>::foo()
nor Foo<int>::foo()
are used in a way that would otherwise cause an implicit instantiation, as per [dcl.spec.auto]/14(1).
Now, consider if we defined a friend function at its friend declaration in the class template Foo
:
template <class T>
struct Foo {
static const auto& foo() { static T t; return t; }
friend void bar() { }
};
void bar();
If any more than one specialization of Foo
is instantiated in the same translation unit, [basic.def.odr]/1 will be violated:
No translation unit shall contain more than one definition of any variable, function, class type, enumeration type, or template.
as the friend bar()
would be re-defined(2) for each specialization that is instantiated.
According to the argument above, the explicit instantiation declarations of the two member function (of class template) specializations should not lead to any instantiation of the associated class template (as per [dcl.spec.auto]/14), meaning the following program should also arguably be well-formed:
template <class T>
struct Foo {
static const auto& foo() { static T t; return t; }
friend void bar() { }
};
void bar();
extern template const auto& Foo<void>::foo();
extern template const auto& Foo<int>::foo();
int main() {}
However, both Clang (10.0.0) and GCC (10.1.0) rejects the program (C++14, C++17, C++2a) with a "redefinition of void bar()
” error:
Clang
error: redefinition of
bar
note: in instantiation of template class
Foo<int>
requested here:extern template const auto& Foo<int>::foo();
GCC
In instantiation of
struct Foo<int>
:error: redefinition of
void bar()
But I never requested (or, afaict, used these specializations in a way such that) the Foo<int>
or Foo<void>
specializations (are) to be instantiated.
Thus to the question:
- Is the program (with the friend) above well-formed, or are the compilers correct to instantiate the class template specializations and subsequently reject the program?
(1) Note the the same question (and compiler behaviour) applies even if foo()
is not declared using a placeholder type, but then we would not be able to fall back on the explicitness of [dcl.spec.auto]/14, but we may not need to.
(2) As friends defined at their friend declaration are inline, we may actually instantiate different specializations in different translation units and still respect ODR, but this is not relevant in this discussion.