class 模板的成员函数的显式实例化声明是否会导致 class 模板的实例化?

Does an explicit instantiation declaration of a member function of a class template cause instantiation of the class template?

[dcl.spec.auto]/14 声明 [强调 我的]:

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 ]

[temp.explicit]/11声明[强调我的]:

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.

现在,考虑以下程序:

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() {}

这是合式的; [temp.explicit]/11 不适用,因为 class 模板专业化实体 Foo<void>::foo()Foo<int>::foo() 的成员函数均未以会导致隐式实例化的方式使用,根据 [=34] =](1).

现在,考虑我们是否在 class 模板中的友元声明处定义了一个友元函数 Foo:

template <class T>
struct Foo {
    static const auto& foo() { static T t; return t; }
    friend void bar() { }
};
void bar();

如果在同一个翻译单元中实例化 Foo 的任何一个以上的特化,将违反 [basic.def.odr]/1

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

因为朋友 bar() 将被重新定义(2) 为实例化的每个特化。

根据上述论点,两个成员函数(class 模板)特化的显式实例化声明不应导致关联的 class 模板的任何实例化(根据 [dcl.spec.auto]/14),这意味着下面的程序也应该是良构的:

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() {}

但是,Clang (10.0.0) 和 GCC (10.1.0) 都拒绝具有“重新定义 void bar() 的程序(C++14、C++17、C++2a) ” 错误:

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()

但我从未要求(或者,afaict,以某种方式使用这些专业化)Foo<int>Foo<void> 专业化(被)实例化。

因此问题:


(1) 请注意,即使 foo() 未使用占位符类型声明,同样的问题(和编译器行为)也适用,但那样我们将无法求助[dcl.spec.auto]/14 的明确性,但我们可能不需要。

(2) 由于在 friend 声明中定义的 friends 是内联的,我们实际上可以在不同的翻译单元中实例化不同的专业化,并且仍然尊重 ODR,但这与本次讨论无关。

class 模板必须实例化的论点是声明 匹配 可能需要了解 class 显然需要实例化的事情。考虑简化的例子

template<class T>
struct A {void f(T) {}};

extern template void A<int>::f(int);

要知道成员函数是否存在,我们必须实例化class模板中的声明,一般情况下不实例化整个class是做不到的:参数类型可以依赖于 class 模板中的任何 other 声明,我们可能需要考虑多个重载甚至进行模板参数推导来决定 f 是什么意思。有人可以争辩说,只有当其中一种情况确实存在时才应该进行实例化,这会误入 CWG2 领域(显然不可能进行实例化),但这个想法是原则上实例化对于 决定是必要的 关于此类问题,因为我们根本不 尝试 首先检查模板本身。