三路比较和constexpr函数模板:哪个编译器是对的?

cpp*_*ner 19 c++ language-lawyer spaceship-operator constexpr c++20

考虑:

#include <compare>

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

unsigned int x;
using T = decltype(x <=> f());
Run Code Online (Sandbox Code Playgroud)

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.
Run Code Online (Sandbox Code Playgroud)

现场演示

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

#include <compare>

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

unsigned x;
auto _ = x <=> f();
using T = decltype(x <=> f());
Run Code Online (Sandbox Code Playgroud)

现场演示

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

cpp*_*ner 12

Clang 根据N4861是正确的。

[temp.inst]/5 :

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

[temp.inst]/8 :

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

[表达式常量]/15 :

如果满足以下条件,则需要一个函数或变量来进行常量评估

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

[表达式常量]/15 :

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

  • 一个明显的常量评估表达式,
  • 一个潜在的评估表达式([basic.def.odr]),
  • 花括号初始化列表的直接子表达式,
  • 出现在模板化实体中的 form & cast-expression 的表达式,或
  • 上述之一的子表达式不是嵌套未计算操作数的子表达式。

[表达式常量]/5 :

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

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

[dcl.init.list]/7 :

缩小转换是隐式转换

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

[expr.spaceship]/4 :

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

  • 如果需要缩小转换,除了从整数类型到浮点类型之外,程序是格式错误的。

[expr.arith.conv] :

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

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

由于x <=> f()indecltype(x <=> f())不满足“潜在常数评估”的标准,f因此不需要“持续评估”。因此,定义的存在f<>不被认为会影响程序的语义。因此,该表达式不会实例化f<>

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

按照通常的算术转换, in x <=> f(), f()(of type int) 被转换为unsigned int. 当f()不是常量表达式时,此转换是收缩转换,这会使程序格式错误。

iff不是函数模板,或者它的定义已经被实例化,则f() 一个常量表达式,并且由于f()fit into的结果,unsigned intf()to的转换unsigned int不是收缩转换,因此程序是良构的。

  • @Barry:鉴于[expr.const \]/15.3](https://timsong-cpp.github.io/cppwp/n4861/expr.const#footnote-81)上的[脚注],我认为这是一个措辞缺陷该项目符号列表中缺少“&lt;=&gt;”。 (6认同)
  • 这是一个奇怪的结果,因为 `f&lt;&gt;` 定义的存在非常明显地影响了这个程序的语义。 (2认同)