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

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 的模板),但我预料到了上班。

一般来说,我是否应该假设模板 类 及其成员函数的约束取决于其模板参数不能显式实例化?

显式 class 模板实例化定义也是在实例化时定义的那些成员的显式实例化定义

考虑以下简化示例:

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 声明 [强调 我的]:

An explicit instantiation that names a class template specialization is also an explicit instantiation of the same kind (declaration or definition) of each of its members (not including members inherited from base classes and members that are templates) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, provided that the associated constraints, if any, of that member are satisfied by the template arguments of the explicit instantiation ([temp.constr.decl], [temp.constr.constr]), except as described below. [...]

这意味着显式实例化定义仅命名 Field 的 class 模板特化,比如

template struct Field<1>;

还将导致满足约束表达式 requires (rank == 1)dot 重载的显式实例化定义,但不会导致具有约束表达式 requires (rank == 2) 的重载。但是,except as described below 部分将我们引向 [temp.explicit]/12,其中指出 [emphasis mine]:

An explicit instantiation definition that names a class template specialization explicitly instantiates the class template specialization and is an explicit instantiation definition of only those members that have been defined at the point of instantiation.

的意思是,对于上面的简化示例(后面是Field<1>的显式实例化定义,如上),上面这段话表示both[=89=的显式实例化定义] 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 class 模板的 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.explicit]/12 引用隐式给出显式实例化定义,因为这似乎为两个成员提供了单独的实例化定义(不尊重约束表达式),因此违反了 ODR。

编译器在 class 模板特化的显式实例化定义与特化的非模板成员函数之间的不同行为有些特殊,但可能的区别在于后一种情况, [temp.constr.constr]/2 应用 [重点 我的]

[...] Overload resolution requires the satisfaction of constraints on functions and function templates.


如果我们只声明但不定义第二个重载,它将不会被实例化为显式实例化定义的一部分(即[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[强调我的]:

The implicit instantiation of a class template specialization causes

(3.1) the implicit instantiation of the declarations, but not of the definitions, of the non-deleted class member functions, member classes, scoped member enumerations, static data members, member templates, and friends; and [...]

使得 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]/4dot 重载不会被实例化,因为 Field<1> 特化未在需要其定义存在的上下文中引用。

然而,最后,我们可能会注意到 Field class 模板的 dot 静态成员函数的隐式实例化将遵守约束表达式,并实例化重载满足特定 class 模板特化的 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 管辖,如上文所述。

这是 GCC 和 Clang 的 c++20 实验性实现的一个错误。

这被报告为 GCC bug #77595

在c++20段落[temp.explicit]/11:

An explicit instantiation that names a class template specialization is also an explicit instantiation of the same kind (declaration or definition) of each of its members (not including members inherited from base classes and members that are templates) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, provided that the associated constraints, if any, of that member are satisfied by the template arguments of the explicit instantiation ([temp.constr.decl], [temp.constr.constr]), except as described below. [...]

根据此 c++20 附录 “前提是满足该成员的相关约束(如果有的话)” 意味着两个 Dot 重载应显式实例化。

c++17 标准中有粗体字句“除了如下所述”。它确实适用于第一个子句“命名为class模板特化的显式实例化也是其每个成员的同类(声明或定义)的显式实例化 ”。这个异常的解释在 c++20 中没有改变。可能委员会忽略了从语法上讲,将例外应用于 c++20 附录可能是正确的。

下面是这一段的c++17 version

An explicit instantiation that names a class template specialization is also an explicit instantiation of the same kind (declaration or definition) of each of its members (not including members inherited from base classes and members that are templates) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, except as described below.

这个短句意思很清楚。