浮点类型作为 C++20 中的模板参数

non*_*741 10 c++ floating-point templates c++20

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

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

我能找到的唯一相关的东西是:P0732R2 非类型模板参数中的类类型。如果有人可以简单地解释一下,那就太好了。

Dav*_*ing 11

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

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

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

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

我们如何决定是否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;
}
Run Code Online (Sandbox Code Playgroud)

并且目前还不清楚做出的事实,这两个对象包含相同的什么(因而比较平等的==,尽管有非常不同的)行为例如,对于迭代器失效)。

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

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

P1185 <=> != ==

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

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

发现类类型 NTTP 和constexprstd::bit_cast的不相关特性允许将浮点值走私到类似std::array<std::byte,sizeof(float)>. 这将导致这样的伎俩语义将是每一个代表float将是一个不同的模板参数,尽管-0.0==0.0和(给予float nan=std::numeric_limits<float>::quiet_NaN();nan!=nan。因此,建议直接允许浮点值作为模板参数,具有这些语义,以避免鼓励广泛采用这种 hacky 解决方法。

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

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

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

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

事实上,在2019年11月GCC已经实施了类型NTTPs基本支持,而不需要任何比较操作。

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

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

P1907R1(结构型)

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

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

一般解决方案

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

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