受约束的成员函数和显式模板实例化
Constrained member functions and explicit template instantiation
G++和Clang++一致认为以下代码段不是有效的C++:
template<int dim, int rank>
struct Tensor {};
template<int dim>
double InnerProduct(Tensor<dim, 1> const &, Tensor<dim, 1> const &)
{ return 0.0; }
template<int dim>
double DoubleInnerProduct(Tensor<dim, 2> const &, Tensor<dim, 2> const &)
{ return 0.0; }
template<int dim, int rank>
class Field
{
private:
static double Dot(Tensor<dim, rank> const &u, Tensor<dim, rank> const &v) requires (rank == 1)
{ return InnerProduct(u, v); }
static double Dot(Tensor<dim, rank> const &u, Tensor<dim, rank> const &v) requires (rank == 2)
{ return DoubleInnerProduct(u, v); }
};
template class Field<2, 1>;
template class Field<2, 2>;
错误消息指出,即使具有未满足约束的函数也被实例化:
error: no matching function for call to ‘DoubleInnerProduct(const Tensor<2, 1>&, const Tensor<2, 1>&)’
22 | { return DoubleInnerProduct(u, v); }
我可以通过多种方式使其工作(例如,将Dot
声明为模板,默认参数等于应用约束的rank
),但我希望它能工作。
通常,我应该假设具有成员函数的模板类不能显式实例化吗?成员函数的约束取决于它们的模板参数?
显式类模板实例化定义也是在实例化时定义的那些成员的显式实例化定义
考虑以下简化示例:
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状态〔emphasismine〕:
一个显式实例化,用于命名类模板专门化也是同类的显式实例化(声明或定义)(不包括成员从作为模板的基类和成员继承)以前没有明确专门从事翻译工作包含显式实例化,提供模板满足该成员的约束(如果有)显式实例化的自变量([temp.contr.dell],〔temp.contr.contr〕),,除非以下所述[…]
这意味着一个只命名Field
的类模板专用化的显式实例化定义,比如
template struct Field<1>;
也将导致满足约束表达式requires (rank == 1)
的dot
重载的显式实例化定义,但不适用于具有约束表达式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) { }
注意,我们可以为Field
类模板的dot
非模板成员提供一个显式实例化定义,特别是后者的特定化,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.dexplict]/12引用隐式地给出显式实例化定义时,情况并非如此,因为这似乎为两个成员提供了单独的实例化定义(不考虑约束表达式),从而违反了ODR。
编译器在类模板专门化的显式实例化定义与专门化的非模板成员函数之间的不同行为有些特殊,但可能的区别是,对于后一种情况,[temp.contr.contr]/2应用[emphasismine]
[…]重载解决需要满足函数和函数模板上的约束。
如果我们只声明而不定义第二个重载,它将不会作为Field<1>
的显式实例化定义的一部分进行实例化(即,[temp.dexplict]/12不适用于它),并且我们将不再有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[emphasismine]:
类模板专业化的隐式实例化导致
(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>
专门化在需要其定义存在的上下文中没有被引用。
然而,最后我们可以注意到,Field
类模板的dot
静态成员函数的隐式实例化将尊重约束表达式,并实例化满足特定类模板专业化的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的控制。
这是GCC和Clang的c++20实验实现的一个bug。
这被报告为GCC错误#77595。
在c++20段中[临时显式]/11:
命名类模板专门化的显式实例化也是其每个成员(不包括从基类继承的成员和作为模板的成员)的同一类型(声明或定义)的显式实例,该成员以前没有在包含显式实例化的翻译单元中显式专门化,条件是该成员的相关约束(如果有的话)由显式实例化的模板参数([temp.contr.dell],[temp.corr.contr])满足,除非下面描述。[…]
根据这个c++20腺嘌呤";只要该成员的相关约束条件(如果有的话)得到满足">意味着两个Dot
过载中只有一个应显式实例化。
粗体条款">,除非下文所述";在c++17标准中存在。它确实适用于第一条">命名类模板专门化的显式实例化也是其每个成员的同类(声明或定义)的显式实例";。这个例外的解释在c++20中没有改变。委员会可能忽略了这一点,即从语法上讲,将异常应用于c++20腺嘌呤可能是正确的。
以下是本段的c++17版本:
命名类模板专门化的显式实例化也是其每个成员(不包括从基类继承的成员和作为模板的成员)的同一类型(声明或定义)的显式实例,该成员以前没有在包含显式实例化的翻译单元中显式专门化,除非以下所述
在这个较短的句子中,意思很清楚。
- 静态数据成员模板专用化的实例化点在哪里
- 我有一个对象,它将在整个程序的持续时间内实例化,但一个类成员不会,我应该动态分配它吗?
- 受约束的成员函数和显式模板实例化
- 实例化多种类型的成员函数模板
- 为什么在使用指针时不采用类成员的默认值,而不是直接实例化对象时?
- 将类成员函数的模板定义放在 CPP 文件中C++隐式实例化而不是 .H 允许吗?
- 如何实例化类的公共成员并将其作为 std::p romise 返回?
- 在实例化封闭类模板之后,我们可以声明模板类成员的部分专用化吗
- 使用 SFINAE 有选择地实例化模板的成员函数
- 静态模板成员函数的实例化?
- 访问使用接口实例化的类的私有成员
- Google Mock:在目标类的构造函数中实例化的模拟私有变量成员
- 参考数据成员到模板的实例化
- 类的私有成员在我的类实例化期间更改,即使他们不应该
- 如果未实例化成员模板,是否要评估static_asserts?
- 实例化与unique_ptr的类集合成员
- 实例化成员模板函数时的Buggy(?)编译器行为
- 类模板的成员函数模板找不到定义,尽管存在显式实例化。不链接
- 如何实例化C++成员字符串引用
- 通过模板参数选择子类与实例化成员变量的区别