【问题标题】:Three-way comparison and constexpr function template: which compiler is right?三路比较和 constexpr 函数模板:哪个编译器是对的?
【发布时间】:2021-02-15 00:24:00
【问题描述】:

考虑:

#include <compare>

template<class=void>
constexpr int f() { return 1; }

unsigned int x;
using T = decltype(x <=> f());

GCC 和 MSVC 接受 T 的声明。 Clang 拒绝它,并显示以下错误消息:

<source>:7:26: error: argument to 'operator<=>' cannot be narrowed from type 'int' to 'unsigned int'
using T = decltype(x <=> f());
                        ^
1 error generated.

(live demo)

如果模板头 (template&lt;class=void&gt;) 被删除,或者如果 f 在声明 T 之前被显式或隐式实例化,则 Clang 接受它。例如,Clang 接受:

#include <compare>

template<class=void>
constexpr int f() { return 1; }

unsigned x;
auto _ = x <=> f();
using T = decltype(x <=> f());

(live demo)

哪个编译器是正确的,为什么?

【问题讨论】:

    标签: c++ language-lawyer constexpr c++20 spaceship-operator


    【解决方案1】:

    根据N4861,Clang 是正确的。

    [temp.inst]/5:

    除非函数模板特化是已声明的特化,否则当在需要函数定义存在的上下文中引用特化或如果定义的存在影响程序的语义时,函数模板特化会被隐式实例化。

    [temp.inst]/8:

    如果变量或函数需要通过表达式 ([expr.const]) 进行常量求值,则认为变量或函数定义的存在会影响程序的语义

    [expr.const]/15:

    如果是,需要一个函数或变量来进行常量评估

    • 一个 constexpr 函数,由一个表达式 ([basic.def.odr]) 命名,该表达式可能是常数计算,或者
    • 一个变量 [...]。

    [expr.const]/15:

    一个表达式或转换是潜在的常量评估如果它是:

    • 一个明显的常数评估表达式,
    • 潜在评估的表达式 ([basic.def.odr]),
    • braced-init-list 的直接子表达式,
    • 在模板化实体中出现的表单和强制转换表达式的表达式,或
    • 上述之一的子表达式不是嵌套未计算操作数的子表达式。

    [expr.const]/5:

    一个表达式E是一个核心常量表达式,除非E的求值,遵循抽象机的规则([intro.execution ]),将评估以下之一:

    • [...]
    • 调用未定义的 constexpr 函数;

    [dcl.init.list]/7:

    窄化转换是隐式转换

    • [...]
    • 从整数类型或无作用域枚举类型到不能表示原始类型的所有值的整数类型,除非源是常量表达式,其值在整数提升后将适合目标类型

    [expr.spaceship]/4:

    如果两个操作数都具有算术类型,或者一个操作数具有整数类型而另一个操作数具有无作用域枚举类型,则通常的算术转换将应用于操作数。 那么:

    • 如果需要窄化转换,而不是从整数类型到浮点类型,则程序格式错误。

    [expr.arith.conv]:

    [T]通常的算术转换[...]定义如下:

    • [...]
    • 否则,应在两个操作数上执行积分提升 ([conv.prom])。 然后将以下规则应用于提升的操作数:
      • [...]
      • 否则,如果无符号整数类型的操作数的秩大于或等于另一个操作数类型的秩,则将有符号整数类型的操作数转换为无符号整数类型的操作数的类型。

    由于decltype(x &lt;=&gt; f()) 中的x &lt;=&gt; f() 不满足“可能持续评估”的标准,因此f 不是“需要持续评估”。因此,不认为f&lt;&gt;定义的存在会影响程序的语义。因此,这个表达式没有实例化f&lt;&gt;的定义。

    因此,在原始示例中,f() 是对未定义的 constexpr 函数的调用,它不是常量表达式。

    根据通常的算术转换,在x &lt;=&gt; f() 中,f()int 类型)被转换为unsigned int。当f() 不是常量表达式时,这种转换是窄化转换,会导致程序格式错误。

    如果f不是函数模板,或者如果它的定义已经被实例化,那么f()一个常量表达式,并且因为f()的结果适合unsigned int ,从f()unsigned int的转换不是缩窄转换,因此程序是良构的。

    【讨论】:

    • 这是一个奇怪的结局,因为f&lt;&gt;的定义的存在很明显影响了这个程序的语义。
    • @Barry:鉴于footnote on [expr.const]/15.3,我认为&lt;=&gt; 从项目符号列表中丢失是一个措辞缺陷。
    • @Barry 我实际上同意这很奇怪,可能是一个缺陷。但这就是规范性措辞所说的。
    • 如果我们将模板函数定义为consteval,是否会使f()成为常量表达式并使初始示例有效?
    • @Fedor 我认为不会。 consteval 函数的未评估调用不是立即调用,因此不是“潜在的常量评估”。
    猜你喜欢
    • 2022-01-19
    • 2018-03-27
    • 2013-11-18
    • 2012-08-27
    • 1970-01-01
    • 2018-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多