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>
专业化(被)实例化。
因此问题:
- 上面的程序(和朋友)是否格式正确,或者编译器是否正确实例化 class 模板特化并随后拒绝该程序?
(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 领域(显然不可能进行实例化),但这个想法是原则上实例化对于 决定是必要的 关于此类问题,因为我们根本不 尝试 首先检查模板本身。
[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>
专业化(被)实例化。
因此问题:
- 上面的程序(和朋友)是否格式正确,或者编译器是否正确实例化 class 模板特化并随后拒绝该程序?
(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 领域(显然不可能进行实例化),但这个想法是原则上实例化对于 决定是必要的 关于此类问题,因为我们根本不 尝试 首先检查模板本身。