首页 > 解决方案 > 浮点类型作为 C++20 中的模板参数

问题描述

根据cppreference , C++20 现在支持模板中的浮点参数。但是,我无法在该站点以及其他站点上找到任何编译器支持信息。当前的 gcc 主干只是这样做,其他的都是负面的。

我只想知道这是否是一个非常低优先级的功能和/或何时期望它得到普遍支持。

我能找到的唯一相关内容是:P0732R2 非类型模板参数中的类类型。如果有人能简单地解释一下,那就谢了。

标签: c++templatesfloating-pointc++20

解决方案


似乎在这里可以回答的真正问题是关于此功能的历史,以便可以在上下文中理解任何编译器支持。

非类型模板参数类型的限制

长期以来,人们一直想要类类型的非类型模板参数。那里的答案有些缺乏;真正使对此类模板参数(实际上,非平凡的用户定义类型)的支持变得复杂的是它们未知的身份概念:给定

struct A {/*...*/};
template<A> struct X {};
constexpr A f() {/*...*/}
constexpr A g() {/*...*/}
X<f()> xf;
X<g()> &xg=xf;  // OK?

我们如何确定是否X<f()>X<g()>同一类型?对于整数,答案似乎很直观,但类类型可能类似于std::vector<int>,在这种情况下,我们可能有

// C++23, if that
using A=std::vector<int>;
constexpr A f() {return {1,2,3};}
constexpr A g() {
  A ret={1,2,3};
  ret.reserve(1000);
  return ret;
}

尽管具有非常不同的行为例如,对于迭代器失效),但不清楚如何理解两个对象包含相同的(因此与 比较相等)。==

P0732非类型模板参数中的类类型

的确,本文首先添加了对类类型非类型模板参数的支持,就new<=>运算符而言。逻辑是默认该运算符的类是“对比较透明的”(使用的术语是“强结构相等”),因此程序员和编译器可以就身份的定义达成一致。

P1185 <=> != ==

后来意识到==出于性能原因应该可以单独默认(例如,它允许提前退出以比较不同长度的字符串),并且根据该运算符重写了强结构相等的定义(它与默认<=>)。这不会影响这个故事,但没有它,线索是不完整的。

P1714 NTTP 不完整,没有 float、double 和 long double!

发现类类型的 NTTP 和constexprstd::bit_cast的不相关特性允许将浮点值偷偷带入类似std::array<std::byte,sizeof(float)>. 这种技巧产生的语义将是 a 的每个表示都是float不同的模板参数,尽管事实上-0.0==0.0and (given float nan=std::numeric_limits<float>::quiet_NaN();) nan!=nan。因此,建议允许浮点值直接作为具有这些语义的模板参数,以避免鼓励广泛采用这种骇人听闻的解决方法。

当时,关于 (给定template<auto> int vt;)x==y可能与&vt<x>==&vt<y>) 不同的想法存在很多混淆,并且该提案被拒绝,因为需要比 C++20 所能提供的更多分析。

P1907R0与非类型模板参数不一致

事实证明,这==方面存在很多问题。甚至枚举(一直被允许作为模板参数类型)也可以重载==,并且将它们用作模板参数只会完全忽略该重载。(这或多或少是必要的:这样的运算符可能在某些翻译单元中定义,而不是在其他翻译单元中定义,或者可能以不同方式定义,或者具有内部链接。)此外,实现需要对模板参数做的事情是规范化它:一个模板参数(例如,调用)与另一个模板参数(例如,显式特化)进行比较需要后者已经以某种方式被识别就前者而言,同时以某种方式允许它们可能不同。

这种身份概念也已经不同于==其他类型。甚至 P0732 也认识到引用(也可以是模板参数的类型)不与 进行比较==,因为当然x==y并不意味着&x==&y. 不太广为人知的是,指向成员的指针也违反了这种对应关系:由于它们在常量求值中的不同行为,指向联合不同成员的指针与模板参数不同,尽管比较==,并且指向成员的指针已被强制转换为指向基类具有相似的行为(尽管它们的比较是未指定的,因此不允许作为常量评估的直接组成部分)。

事实上,在 2019 年 11 月,GCC 已经实现了对类类型 NTTP 的基本支持,而不需要任何比较运算符。

P1837从 C++20 中删除类类型的 NTTP

这些不协调之处如此之多,以至于已经有人提议将整个功能推迟到 C++23。面对如此受欢迎的功能中存在如此多的问题,一个小组被委托指定保存它所需的重大更改。

P1907R1(结构型)

这些关于类类型和浮点类型的模板参数的故事在 P1907R0 的修订版中重新融合,该修订版保留了它的名称,但用同样针对同一主题提交的国家机构评论的解决方案替换了它的正文。(新的)想法是认识到比较从来没有真正密切相关,并且模板参数同一性的唯一一致模型是,如果在持续评估期间有任何方法可以区分两个参数(它具有上述能力区分指向成员的指针)。毕竟,如果两个模板参数产生相同的特化,那么该特化必须有一个行为,并且它必须与直接使用任一参数获得的相同。

虽然支持广泛的类类型是可取的,但在 C++20 的几乎最后一刻引入(或者更确切地说是重写)的新特性能够可靠地支持的唯一类型是那些每个值可以通过实现来区分的客户可以通过其客户来区分——因此,只有那些具有所有公共成员(递归地具有此属性)的客户才能区分。对这种结构类型的限制不如对聚合的限制那么严格,因为任何构造过程都是允许的,只要它是 constexpr。它还为未来的语言版本提供了合理的扩展,以支持更多的类类型,甚至可能std::vector<T>——再次,通过规范化(或序列化)而不是通过比较(不支持此类扩展)。

一般解决方案

这种新发现的理解与 C++20 中的其他任何东西都没有关系。使用此模型的类类型 NTTP 可能是C++11的一部分(它引入了类类型的常量表达式)。支持立即扩展到联合,但逻辑完全不限于类;它还确定,长期以来禁止作为子对象指针或具有浮点类型的模板参数也是出于混淆的动机,==并且是不必要的。(虽然由于技术原因这不允许字符串文字作为模板参数,但它确实允许const char*指向静态字符数组的第一个字符的模板参数。)

换句话说,推动 P1714 的力量最终被认为是模板基本行为的必然数学结果,浮点模板参数最终成为 C++20 的一部分。然而,C++20 的原始提案实际上并没有为 C++20 指定浮点和类类型 NTTP,这使得“编译器支持”文档变得复杂。


推荐阅读