llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
29.04k stars 11.98k forks source link

Unevaluated operand evaluated within requires expression #93364

Open Tsche opened 5 months ago

Tsche commented 5 months ago

Consider the following code example:

template <typename>
inline constexpr bool dependent_false = false;

struct Universal {
    template <typename T>
    constexpr operator T() { 
        static_assert(dependent_false<T>, "operator T can only be used in unevaluated contexts" ); 
    }
};

static_assert(requires { int{Universal{}}; });

https://godbolt.org/z/583rWYG61

GCC and MSVC accept this. Clang errors with

error: static assertion failed due to requirement 'dependent_false<int>': operator T can only be used in unevaluated contexts
    7 |         static_assert(dependent_false<T>, "operator T can only be used in unevaluated contexts" ); 
      |                       ^~~~~~~~~~~~~~~~~~
<source>:11:30: note: in instantiation of function template specialization 'Universal::operator int<int>' requested here
   11 | static_assert(requires { int{Universal{}}; });

According to [expr.prim.req]/2 expressions within a requirement-body are unevaluated operands and should therefore not be evaluated ([expr.context]/1).

Furthermore

In an unevaluated operand, a non-static class member can be named ([expr.prim.id]) and naming of objects or functions does not, by itself, require that a definition be provided ([basic.def.odr])

since no definition is required, Universal::operator int<int> should not be instantiated.

llvmbot commented 5 months ago

@llvm/issue-subscribers-clang-frontend

Author: None (Tsche)

Consider the following code example: ```cpp template <typename> inline constexpr bool dependent_false = false; struct Universal { template <typename T> constexpr operator T() { static_assert(dependent_false<T>, "operator T can only be used in unevaluated contexts" ); } }; static_assert(requires { int{Universal{}}; }); ``` https://godbolt.org/z/583rWYG61 GCC and MSVC accept this. Clang errors with ``` error: static assertion failed due to requirement 'dependent_false<int>': operator T can only be used in unevaluated contexts 7 | static_assert(dependent_false<T>, "operator T can only be used in unevaluated contexts" ); | ^~~~~~~~~~~~~~~~~~ <source>:11:30: note: in instantiation of function template specialization 'Universal::operator int<int>' requested here 11 | static_assert(requires { int{Universal{}}; }); ``` According to [[expr.prim.req]/2](https://standards.pydong.org/c++/expr.prim.req#general-2) expressions within a requirement-body are unevaluated operands and should therefore not be evaluated ([[expr.context]/1](https://standards.pydong.org/c++/expr.context#1)). Furthermore > In an unevaluated operand, a non-static class member can be named ([[expr.prim.id]](https://standards.pydong.org/c++/expr.prim.id)) and naming of objects or functions does not, by itself, require that a definition be provided ([[basic.def.odr]](https://standards.pydong.org/c++/basic.def.odr)) since no definition is required, `Universal::operator int<int>` should not be instantiated.
MitalAshok commented 5 months ago

https://wg21.link/expr.const#21

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

  • [...]
  • an immediate subexpression of a braced-init-list,72
  • [...]

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

  • a constexpr function that is named by an expression that is potentially constant evaluated, or
  • [...]

https://wg21.link/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]), even if constant evaluation of the expression is not required or if constant expression evaluation does not use the definition.

https://wg21.link/temp.inst#4

the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist or if the existence of the definition of the member affects the semantics of the program

It appears that Clang is showing the expected behaviour, and GCC and MSVC should be instantiating the conversion operator.

(The "fix" is to not mark the conversion operator constexpr)

MitalAshok commented 5 months ago

Note that this wording comes from CWG1581 / P0859R0 as a DR so should be applied to C++11/14/17 even if the wording only appears in C++20