21

考虑:

#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.

现场演示

如果 template-head ( 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());

现场演示

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

4

1 回答 1

13

Clang 根据N4861是正确的。

[温度.inst]/5

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

[温度.inst]/8

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

[expr.const]/15

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

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

[expr.const]/15

表达式或转换在以下情况下可能会被评估为常量

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

[expr.const]/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()(类型int)转换为unsigned int. 当f()不是一个常量表达式时,这种转换是一种窄化转换,它会导致程序格式错误。

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

于 2020-11-02T10:40:21.033 回答