显式类模板实例化定义也是在实例化点定义的那些成员的显式实例化定义
考虑以下简化示例:
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); }
};
[临时显式]/11 http://eel.is/c++draft/temp.explicit#11状态 [emphasis mine]:
显式实例化命名类模板专业化
也是一个显式实例化同类(声明或
定义)其每个成员的(不包括会员
从基类和模板成员继承)
以前没有明确专门从事翻译单元
包含显式实例化,前提是相关的
限制条件,如果有的话,该成员对模板感到满意
显式实例化的参数([temp.constr.decl],
[临时.构造.构造]),除下述情况外。 [...]
这意味着仅命名类模板特化的显式实例化定义Field
, say
template struct Field<1>;
还将导致显式实例化定义dot
满足约束表达式的重载 requires (rank == 1)
,但不适用于带有约束表达式的重载requires (rank == 2)
。但是,那除下述情况外部分引导我们[临时显式]/12 https://eel.is/c++draft/temp.explicit#12,其中指出 [emphasis mine]:
命名类模板的显式实例化定义
专业化显式实例化类模板
专业化并且是显式实例化定义仅有的
那些在实例化时定义的成员.
这意味着,对于上面的简化示例(后跟显式实例化定义)Field<1>
,如上),上面的段落表明了显式实例化定义both 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 。
类模板专业化的显式实例化定义与专业化的非模板成员函数之间编译器的不同行为有些特殊,但可能的区别在于,对于后一种情况,[临时构造.构造]/2 http://eel.is/c++draft/temp.constr.constr#2.sentence-3适用 [emphasis mine]
[...] 重载解决需要满足功能约束和函数模板。
如果我们只declare但不要定义第二个重载,它不会作为显式实例化定义的一部分进行实例化(即,[临时显式]/12 https://eel.is/c++draft/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() {}
现在,为什么隐式实例化不会失败?
As per [温度设置]/3 https://eel.is/c++draft/temp.inst#3.1 [emphasis mine]:
类模板特化的隐式实例化导致
(3.1) 声明的隐式实例化,但不属于
定义,非删除类成员函数中,member
类、作用域成员枚举、静态数据成员、成员
模板和朋友;和 [...]
这样 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;
}
其中,根据[温度设置]/4 https://eel.is/c++draft/temp.inst#4, the 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"
}
这可能是由[临时构造.构造]/2 http://eel.is/c++draft/temp.constr.constr#2.sentence-3,如上面引用的。