问题
[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.
回答1:
The argument that the class template must be instantiated is that declaration matching may need to know things about the class that plainly require instantiation. Consider the simplified example
template<class T>
struct A {void f(T) {}};
extern template void A<int>::f(int);
To know whether the member function exists, we must instantiate the declaration in the class template, and we can’t do that in general without instantiating the whole class: the parameter type could depend on any other declarations in the class template, and we might need to consider multiple overloads or even do template argument deduction to decide which f
is meant. One can argue that instantiation should happen only if one of these situations actually pertains, which strays into CWG2 territory (where instantiation is obviously impossible), but the idea is that instantiation is necessary in principle to decide about such questions because we simply don’t try examining the template itself first.
来源:https://stackoverflow.com/questions/64917707/does-an-explicit-instantiation-declaration-of-a-member-function-of-a-class-templ