首页 > 解决方案 > 类模板的成员函数的显式实例化声明是否会导致类模板的实例化?

问题描述

[dcl.spec.auto]/14状态 [强调我的]:

显式实例化声明不会导致使用占位符 type 声明的实体的实例化,但它也不会阻止根据需要实例化该实体以确定其类型。[ <em>例子:

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

 — <em>结束示例]

[temp.explicit]/11状态 [强调我的]:

作为显式实例化声明的主体并且也以其他方式在翻译单元中导致隐式实例化的方式使用的实体应是程序中某处的显式实例化定义的主体;否则程序格式错误,不需要诊断。

现在,考虑以下程序:

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

这是格式良好的;根据[dcl.spec.auto]/14 (1) , [temp.explicit]/11既不作为类模板特化实体的成员函数应用,Foo<void>::foo()也不Foo<int>::foo()以会导致隐式实例化的方式使用。

现在,考虑我们是否在类模板的友元声明中定义了友元函数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将被违反:

任何翻译单元都不得包含一个以上的任何变量、函数、类类型、枚举类型或模板的定义。

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

根据上面的论点,两个成员函数(类模板)特化的显式实例化声明不应导致关联类模板的任何实例化(根据[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) 都拒绝程序(C++14、C++17、C++2a)并出现“重新定义void bar()”错误:

错误:重新定义bar

Foo<int>注意:在此处请求 的模板类的实例化中:extern template const auto& Foo<int>::foo();

海合会

在实例化struct Foo<int>

错误:重新定义void bar()

但是我从来没有要求(或者,afaict,以某种方式使用这些特化)Foo<int>或者特Foo<void>化(是)被实例化。

因此对于这个问题:


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

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

标签: c++templateslanguage-lawyerinstantiationfriend

解决方案


类模板必须被实例化的论点是声明匹配可能需要知道关于类的那些显然需要实例化的事情。考虑简化的例子

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

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

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


推荐阅读