【问题标题】:Constrained member functions and explicit template instantiation受约束的成员函数和显式模板实例化
【发布时间】:2020-06-30 14:28:46
【问题描述】:

G++ 和 Clang++ 一致认为以下 sn-p 不是有效的 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 并应用约束),但我希望它能够工作。

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

【问题讨论】:

  • 错误信息是什么?
  • 观察(非解释):(A) 如果您只是声明 Dot 重载,Field&lt;2, 1&gt; 的显式实例化声明和Field&lt;2, 2&gt; 编译成功。 (B) 即使不删除定义,您也可以显式声明对给定实例化可行的完整静态成员函数,而不仅仅是特定类模板的实例化声明:template double Field&lt;2, 1&gt;::Dot(Tensor&lt;2, 1&gt; const &amp;, Tensor&lt;2, 1&gt; const &amp;);
  • [temp.explicit]/10 声明 “需要一个受约束模板的显式实例化才能满足该模板的相关约束。[...]” 这可能适用于此处,在您可以成功地为正确的Dot 重载提供明确的实例化定义(如我上面的评论所示)。
  • 此外,[temp.explicit]/12 声明:“显式实例化定义命名类模板特化显式实例化类模板特化并且是显式实例化定义仅对那些已经在实例化点定义。”,这解释了为什么 Clang 和 GCC 接受 Field 类模板的显式实例化定义,因为给定的(静态)成员函数实例化无效尚未定义。
  • 您是否有理由明确实例化例如template class Field&lt;2, 2&gt;; 而不是仅仅声明一个变量 Field&lt;2, 2&gt; foo?只是为了了解会发生什么吗?

标签: c++ language-lawyer c++20 c++-concepts explicit-instantiation


【解决方案1】:

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

考虑以下简化示例:

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&lt;1&gt; 的显式实例化定义,如上),上面的段落表示 both dot 重载的显式实例化定义,如两者都在Field&lt;1&gt; 的显式实例化定义处定义。然而,这意味着违反 ODR,因为Field&lt;1&gt;::void dot(A&lt;1&gt;) 有两个定义。

// 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.explicit]/12 引用隐式给出显式实例化定义,因为这似乎为两个成员提供了单独的实例化定义(不考虑约束表达式)和因此违反了 ODR。

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

[...] 重载解决需要满足对函数的约束和函数模板。


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

类模板特化的隐式实例化原因

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

然而,最后,我们可能注意到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"
}

这可能由[temp.constr.constr]/2 管理,如上所述。

【讨论】:

  • “SFINAE ... 约束表达式需要应用于依赖名称” 与 SFINAE 不同,requires 可以禁用非模板模板类的成员函数。如果您删除显式实例化并尝试调用该函数,它确实有效。
  • @HolyBlackCat 我刚刚注意到情况确实如此(DEMO),但不确定这是否真的有效还是违反了 ODR?我想,根据您的评论,这是约束表达式和元编程 SFINAE 之间的不同之处?你知道哪些部分(可能是[temp.const])控制了这一点吗?
  • @HolyBlackCat 从您的评论和我上面的回答(第一部分)看来,1)类模板的非模板成员函数的显式实例化定义将不遵守 requires 子句,而 2 ) 如果它们被隐式实例化,根据 [temp.inst]/4,在决定要实例化哪个成员重载时(根据需要)将遵守约束表达式?
  • 我没有过多研究这个功能,但您的第二条评论听起来是正确的。
  • @metalfox 是的,现在我们也有来自AndyG's answer[temp.explicit]/11,这将激发我们反对的论点。我完全同意这个标准的措辞很棘手。
【解决方案2】:

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

报告为 GCC bug #77595

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

命名类模板特化的显式实例化也是其每个成员(不包括从基类继承的成员和模板成员)的同种(声明或定义)的显式实例化,以前没有在包含显式实例化的翻译单元中显式特化,前提是该成员的相关约束(如果有)满足显式实例化的模板参数([temp.constr.decl],[ temp.constr.constr]),下文所述除外。 [...]

根据这个 c++20 附录 “只要满足该成员的相关约束(如果有)” 意味着两个 Dot 重载中只有一个应该被显式实例化。

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

以下是本段的c++17 version

命名类模板特化的显式实例化也是其每个成员(不包括从基类继承的成员和模板成员)的同种(声明或定义)的显式实例化,以前没有显式特化包含显式实例化的翻译单元,除了下面描述的情况。

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

【讨论】:

  • 非常有说服力。那个“除了下面描述的”确实令人困惑,但如果你知道它已经在那里......虽然没有看到这个错误,但很奇怪。 A. Sutton 和 GCC 团队修复了 GCC 10 版本的大部分概念错误。
  • 已接受为 GCC 10.3 修复它的补丁:gcc.gnu.org/pipermail/gcc-patches/2020-July/550980.html
猜你喜欢
  • 1970-01-01
  • 2019-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多