简体   繁体   中英

Three-way comparison and constexpr function template: which compiler is right?

Consider:

#include <compare>

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

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

GCC and MSVC accept the declaration of T . Clang rejects it, with the following error message:

 <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 )

If the template-head ( template<class=void> ) is removed, or if f is explicitly or implicitly instantiated before the declaration of T , then Clang accepts it. For example, Clang accepts:

#include <compare>

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

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

( live demo )

Which compiler is correct, and why?

Clang is correct per N4861 .

[temp.inst]/5 :

Unless a function template specialization is a declared specialization, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist or if the existence of the definition affects the semantics of the program.

[temp.inst]/8 :

The existence of a definition of a variable or function is considered to affect the semantics of the program if the variable or function is needed for constant evaluation by an expression ([expr.const])

[expr.const]/15 :

A function or variable is needed for constant evaluation if it is:

  • a constexpr function that is named by an expression ([basic.def.odr]) that is potentially constant evaluated, or
  • a variable [...].

[expr.const]/15 :

An expression or conversion is potentially constant evaluated if it is:

  • a manifestly constant-evaluated expression,
  • a potentially-evaluated expression ([basic.def.odr]),
  • an immediate subexpression of a braced-init-list,
  • an expression of the form & cast-expression that occurs within a templated entity, or
  • a subexpression of one of the above that is not a subexpression of a nested unevaluated operand.

[expr.const]/5 :

An expression E is a core constant expression unless the evaluation of E , following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:

  • [...]
  • an invocation of an undefined constexpr function;

[dcl.init.list]/7 :

A narrowing conversion is an implicit conversion

  • [...]
  • from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression whose value after integral promotions will fit into the target type

[expr.spaceship]/4 :

If both operands have arithmetic types, or one operand has integral type and the other operand has unscoped enumeration type, the usual arithmetic conversions are applied to the operands. Then:

  • If a narrowing conversion is required, other than from an integral type to a floating-point type, the program is ill-formed.

[expr.arith.conv] :

[T]he usual arithmetic conversions [...] are defined as follows:

  • [...]
  • Otherwise, the integral promotions ([conv.prom]) shall be performed on both operands. Then the following rules shall be applied to the promoted operands:
    • [...]
    • Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type.

Since x <=> f() in decltype(x <=> f()) does not satisfy the criteria of being "potentially constant evaluated", f is not "needed for constant evaluation". Therefore, the existence of the definition of f<> is not considered to affect the semantics of the program. Therefore, this expression does not instantiate the definition of f<> .

Therefore, in the original example, f() is a call to undefined constexpr function, which is not a constant expression.

Per the usual arithmetic conversions, in x <=> f() , f() (of type int ) is converted to unsigned int . When f() is not a constant expression, this conversion is a narrowing conversion, which renders the program ill-formed.

If f is not a function template, or if its definition has been instantiated, then f() is a constant expression, and because the result of f() fits into unsigned int , the conversion from f() to unsigned int is not a narrowing conversion, and thus the program is well-formed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM