显式类模板实例化定义也是在实例化点定义的那些成员的显式实例化定义
考虑以下简化示例:
template<int rank>
struct A {};
template<int rank>
struct Field {
void dot(A<rank>) requires (rank == 1) { (void)(1); }
void dot(A<rank>) requires (rank == 2) { (void)(2); }
};
[temp.explicit]/11状态 [强调我的]:
命名类模板特化的显式实例化也是其先前未显式特化的每个成员(不包括从基类继承的成员和模板成员)的同类(声明或定义)的显式实例化包含显式实例化的翻译单元,前提是该成员的关联约束(如果有)由显式实例化的模板参数([temp.constr.decl],[temp.constr.constr])满足,除非是如下面所描述的。[...]
这意味着一个显式的实例化定义,它只命名一个类模板特化Field
,比如说
template struct Field<1>;
也将导致dot
满足约束表达式的重载的显式实例化定义 requires (rank == 1)
,但不适用于具有约束表达式的重载requires (rank == 2)
。但是,除了下面描述的部分将我们引向[temp.explicit]/12,其中指出 [强调我的]:
命名类模板特化的显式实例化定义显式实例化类模板特化,并且是仅在实例化点已定义的那些成员的显式实例化定义。
这意味着,对于上面的简化示例(随后是 的显式实例化定义Field<1>
,如上),上面的段落指示了两个 dot
重载的显式实例化定义,因为两者都已在 的显式实例化定义处定义Field<1>
。然而,这意味着违反 ODR,因为将有两个定义Field<1>::void dot(A<1>)
。
// Not OK.
template<int rank>
struct A { };
template<int rank>
struct Field {
void dot(A<rank>) requires (rank == 1) { (void)(1); }
void dot(A<rank>) requires (rank == 2) { (void)(2); }
};
template struct Field<1>;
int main() {}
在 Clang 上产生以下错误:
error: definition with same mangled name '_ZN5FieldILi1EE3dotE1AILi1EE' as another definition
void dot(A<rank>) requires (rank == 2) { }
请注意,我们可以为类模板的给定特化提供明确的实例化定义,特别是类模板的dot
非模板成员Field
,并且 GCC 和 Clang 将很乐意接受它,这表明在显式实例化重载,受约束的函数:
// OK.
template<int rank>
struct A { };
template<int rank>
struct Field {
void dot(A<rank>) requires (rank == 1) { (void)(1); }
void dot(A<rank>) requires (rank == 2) { (void)(2); }
};
template void Field<1>::dot(A<1>);
int main() {}
但如上所述,当它们根据上面的 [temp.explicit]/12 引用隐式给出显式实例化定义时,则不会,因为这似乎为两个成员提供了单独的实例化定义(不考虑约束表达式),因此违反了 ODR .
类模板特化的显式实例化定义与特化的非模板成员函数之间编译器的不同行为有些特殊,但可能的区别在于后一种情况, [temp.constr.constr]/2适用[强调我的]
[...]重载解决需要满足对函数和函数模板的约束。
如果我们只声明但不定义第二个重载,它将不会被实例化为 的显式实例化定义(即[temp.explicit]/12不适用)的一部分Field<1>
,我们将不再有违反 ODR:
// OK.
template<int rank>
struct A { };
template<int rank>
struct Field {
void dot(A<rank>) requires (rank == 1) { (void)(1); }
void dot(A<rank>) requires (rank == 2);
};
template struct Field<1>;
int main() {}
现在,为什么这对于隐式实例化没有失败?
根据[temp.inst]/3 [强调我的]:
类模板特化的隐式实例化导致
(3.1)非删除类成员函数、成员类、范围成员枚举、静态数据成员、成员模板和朋友的声明的隐式实例化,但不是定义的隐式实例化;和 [...]
使得 Clang 和 GCC 都接受以下示例:
template<int rank>
struct A { };
template<int rank>
struct Field {
void dot(A<rank>) requires (rank == 1) { (void)(1); }
void dot(A<rank>) requires (rank == 2) { (void)(2); }
};
int main() {
Field<1> f{};
(void)f;
}
其中,根据[temp.inst]/4,dot
重载将不会被实例化,因为在Field<1>
需要其定义存在的上下文中未引用特化。
然而,最后,我们可能会注意到类模板的dot
静态成员函数的隐式实例化Field
将尊重约束表达式,并实例化满足rank
特定类模板特化的非模板参数约束的重载:
#include <iostream>
template<int rank>
struct A { };
template<int rank>
struct Field {
void dot(A<rank>) requires (rank == 1) { std::cout << "1"; }
void dot(A<rank>) requires (rank == 2) { std::cout << "2"; }
};
int main() {
Field<1>{}.dot(A<1>{}); // "1"
}
如上所述,这可能由[temp.constr.constr]/2 控制。