受约束的成员函数和显式模板实例化

Constrained member functions and explicit template instantiation

本文关键字:实例化 成员 函数 受约束      更新时间:2023-10-16

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版本:

命名类模板专门化的显式实例化也是其每个成员(不包括从基类继承的成员和作为模板的成员)的同一类型(声明或定义)的显式实例,该成员以前没有在包含显式实例化的翻译单元中显式专门化,除非以下所述

在这个较短的句子中,意思很清楚。