2023-07-10 08:54:15 +02:00
// RUN: %clang_cc1 -std=c++2a -Wno-unused-value %s -verify
// RUN: %clang_cc1 -std=c++2b -Wno-unused-value %s -verify
2023-05-08 12:18:43 +02:00
consteval int id ( int i ) { return i ; }
constexpr char id ( char c ) { return c ; }
template < typename T >
constexpr int f ( T t ) { // expected-note {{declared here}}
return t + id ( t ) ; // expected-note 2{{'f<int>' is an immediate function because its body contains a call to a consteval function 'id' and that call is not a constant expression}}
}
namespace examples {
auto a = & f < char > ; // ok, f<char> is not an immediate function
auto b = & f < int > ; // expected-error {{cannot take address of immediate function 'f<int>' outside of an immediate invocation}}
static_assert ( f ( 3 ) = = 6 ) ; // ok
template < typename T >
constexpr int g ( T t ) { // g<int> is not an immediate function
return t + id ( 42 ) ; // because id(42) is already a constant
}
template < typename T , typename F >
constexpr bool is_not ( T t , F f ) {
return not f ( t ) ;
}
consteval bool is_even ( int i ) { return i % 2 = = 0 ; }
static_assert ( is_not ( 5 , is_even ) ) ;
int x = 0 ; // expected-note {{declared here}}
template < typename T >
constexpr T h ( T t = id ( x ) ) { / / expected - note { { read of non - const variable ' x ' is not allowed in a constant expression } } \
2023-07-10 08:54:15 +02:00
// expected-note {{'hh<int>' is an immediate function because its body contains a call to a consteval function 'id' and that call is not a constant expression}}
2023-05-08 12:18:43 +02:00
return t ;
}
template < typename T >
constexpr T hh ( ) { // hh<int> is an immediate function
[[maybe_unused]] auto x = h < T > ( ) ;
return h < T > ( ) ;
}
int i = hh < int > ( ) ; / / expected - error { { call to immediate function ' examples : : hh < int > ' is not a constant expression } } \
2023-07-31 17:05:56 +09:00
// expected-note {{in call to 'hh<int>()'}}
2023-05-08 12:18:43 +02:00
struct A {
int x ;
int y = id ( x ) ;
} ;
template < typename T >
constexpr int k ( int ) {
return A ( 42 ) . y ;
}
}
namespace nested {
template < typename T >
constexpr int fdupe ( T t ) {
return id ( t ) ;
}
struct a {
constexpr a ( int ) { }
} ;
a aa ( fdupe < int > ( ( f < int > ( 7 ) ) ) ) ;
template < typename T >
constexpr int foo ( T t ) ; // expected-note {{declared here}}
a bb ( f < int > ( foo < int > ( 7 ) ) ) ; / / expected - error { { call to immediate function ' f < int > ' is not a constant expression } } \
// expected-note{{undefined function 'foo<int>' cannot be used in a constant expression}}
}
namespace e2 {
template < typename T >
constexpr int f ( T t ) ;
auto a = & f < char > ;
auto b = & f < int > ;
}
namespace forward_declare_constexpr {
template < typename T >
constexpr int f ( T t ) ;
auto a = & f < char > ;
auto b = & f < int > ;
template < typename T >
constexpr int f ( T t ) {
return id ( 0 ) ;
}
}
namespace forward_declare_consteval {
template < typename T >
2023-09-12 10:30:59 -07:00
constexpr int f ( T t ) ;
2023-05-08 12:18:43 +02:00
auto a = & f < char > ;
auto b = & f < int > ; / / expected - error { { immediate function ' f < int > ' used before it is defined } } \
// expected-note {{in instantiation of function template specialization}}
template < typename T >
2023-09-12 10:30:59 -07:00
constexpr int f ( T t ) { // expected-note {{'f<int>' defined here}}
2023-05-08 12:18:43 +02:00
return id ( t ) ; // expected-note {{'f<int>' is an immediate function because its body contains a call to a consteval function 'id' and that call is not a constant expression}}
}
}
namespace constructors {
consteval int f ( int ) {
return 0 ;
}
struct S {
constexpr S ( auto i ) {
f ( i ) ;
}
} ;
constexpr void g ( auto i ) {
[[maybe_unused]] S s { i } ;
}
void test ( ) {
g ( 0 ) ;
}
}
namespace aggregate {
consteval int f ( int ) ;
struct S {
int a = 0 ;
int b = f ( a ) ;
} ;
constexpr bool test ( auto i ) {
S s { i } ;
return s . b = = 2 * i ;
}
consteval int f ( int i ) {
return 2 * i ;
}
void test ( ) {
static_assert ( test ( 42 ) ) ;
}
}
namespace ConstevalConstructor {
int x = 0 ; // expected-note {{declared here}}
struct S {
consteval S ( int ) { } ;
} ;
constexpr int g ( auto t ) {
S s ( t ) ; // expected-note {{'g<int>' is an immediate function because its body contains a call to a consteval constructor 'S' and that call is not a constant expression}}
return 0 ;
}
int i = g ( x ) ; / / expected - error { { call to immediate function ' ConstevalConstructor : : g < int > ' is not a constant expression } } \
// expected-note {{read of non-const variable 'x' is not allowed in a constant expression}}
}
2023-07-10 08:54:15 +02:00
namespace Aggregate {
consteval int f ( int ) ; // expected-note {{declared here}}
struct S {
int x = f ( 42 ) ; / / expected - note { { undefined function ' f ' cannot be used in a constant expression } } \
// expected-note {{'immediate<int>' is an immediate function because its body contains a call to a consteval function 'f' and that call is not a constant expression}}
} ;
constexpr S immediate ( auto ) {
return S { } ;
}
void test_runtime ( ) {
( void ) immediate ( 0 ) ; / / expected - error { { call to immediate function ' Aggregate : : immediate < int > ' is not a constant expression } } \
2023-07-31 17:05:56 +09:00
// expected-note {{in call to 'immediate<int>(0)'}}
2023-07-10 08:54:15 +02:00
}
consteval int f ( int i ) {
return i ;
}
consteval void test ( ) {
constexpr S s = immediate ( 0 ) ;
static_assert ( s . x = = 42 ) ;
}
}
namespace GH63742 {
void side_effect ( ) ; // expected-note {{declared here}}
consteval int f ( int x ) {
if ( ! x ) side_effect ( ) ; // expected-note {{non-constexpr function 'side_effect' cannot be used in a constant expression}}
return x ;
}
struct SS {
int y = f ( 1 ) ; // Ok
int x = f ( 0 ) ; / / expected - error { { call to consteval function ' GH63742 : : f ' is not a constant expression } } \
/ / expected - note { { declared here } } \
// expected-note {{in call to 'f(0)'}}
SS ( ) ;
} ;
SS : : SS ( ) { } // expected-note {{in the default initializer of 'x'}}
consteval int f2 ( int x ) {
2023-09-27 18:41:11 +02:00
if ( ! __builtin_is_constant_evaluated ( ) ) side_effect ( ) ;
2023-07-10 08:54:15 +02:00
return x ;
}
struct S2 {
int x = f2 ( 0 ) ;
constexpr S2 ( ) ;
} ;
constexpr S2 : : S2 ( ) { }
S2 s = { } ;
constinit S2 s2 = { } ;
struct S3 {
int x = f2 ( 0 ) ;
S3 ( ) ;
} ;
S3 : : S3 ( ) { }
}
namespace Defaulted {
consteval int f ( int x ) ;
struct SS {
int x = f ( 0 ) ;
SS ( ) = default ;
} ;
}
namespace DefaultedUse {
consteval int f ( int x ) ; // expected-note {{declared here}}
struct SS {
int a = sizeof ( f ( 0 ) ) ; // Ok
int x = f ( 0 ) ; // expected-note {{undefined function 'f' cannot be used in a constant expression}}
SS ( ) = default ; // expected-note {{'SS' is an immediate constructor because the default initializer of 'x' contains a call to a consteval function 'f' and that call is not a constant expression}}
} ;
void test ( ) {
[[maybe_unused]] SS s ; / / expected - error { { call to immediate function ' DefaultedUse : : SS : : SS ' is not a constant expression } } \
// expected-note {{in call to 'SS()'}}
}
}
namespace UserDefinedConstructors {
consteval int f ( int x ) {
return x ;
}
extern int NonConst ; // expected-note 2{{declared here}}
struct ConstevalCtr {
int y ;
int x = f ( y ) ;
consteval ConstevalCtr ( int yy )
: y ( f ( yy ) ) { }
} ;
ConstevalCtr c1 ( 1 ) ;
ConstevalCtr c2 ( NonConst ) ;
/ / expected - error @ - 1 { { call to consteval function ' UserDefinedConstructors : : ConstevalCtr : : ConstevalCtr ' is not a constant expression } } \
// expected-note@-1 {{read of non-const variable 'NonConst' is not allowed in a constant expression}}
struct ImmediateEscalating {
int y ;
int x = f ( y ) ;
template < typename T >
constexpr ImmediateEscalating ( T yy ) / / expected - note { { ImmediateEscalating < int > ' is an immediate constructor because the initializer of ' y ' contains a call to a consteval function ' f ' and that call is not a constant expression } }
: y ( f ( yy ) ) { }
} ;
ImmediateEscalating c3 ( 1 ) ;
ImmediateEscalating c4 ( NonConst ) ;
/ / expected - error @ - 1 { { call to immediate function ' UserDefinedConstructors : : ImmediateEscalating : : ImmediateEscalating < int > ' is not a constant expression } } \
// expected-note@-1 {{read of non-const variable 'NonConst' is not allowed in a constant expression}}
struct NonEscalating {
int y ;
int x = f ( this - > y ) ; / / expected - error { { call to consteval function ' UserDefinedConstructors : : f ' is not a constant expression } } \
/ / expected - note { { declared here } } \
// expected-note {{use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function}}
constexpr NonEscalating ( int yy ) : y ( yy ) { } // expected-note {{in the default initializer of 'x'}}
} ;
NonEscalating s = { 1 } ;
}
namespace AggregateInit {
consteval int f ( int x ) {
return x ;
}
struct S {
int i ;
int j = f ( i ) ;
} ;
constexpr S test ( auto ) {
return { } ;
}
S s = test ( 0 ) ;
}
namespace GlobalAggregateInit {
consteval int f ( int x ) {
return x ;
}
struct S {
int i ;
int j = f ( i ) ; / / expected - error { { call to consteval function ' GlobalAggregateInit : : f ' is not a constant expression } } \
/ / expected - note { { implicit use of ' this ' pointer is only allowed within the evaluation of a call to a ' constexpr ' member function } } \
// expected-note {{declared here}}
} ;
S s ( 0 ) ; // expected-note {{in the default initializer of 'j'}}
}
2023-09-19 00:24:43 +02:00
2023-09-19 23:41:51 +02:00
namespace GH65985 {
2023-09-27 18:41:11 +02:00
consteval int invalid ( ) ; // expected-note 2{{declared here}}
2023-09-19 23:41:51 +02:00
constexpr int escalating ( auto ) {
return invalid ( ) ;
2023-09-27 18:41:11 +02:00
// expected-note@-1 {{'escalating<int>' is an immediate function because its body contains a call to a consteval function 'invalid' and that call is not a constant expression}}
// expected-note@-2 2{{undefined function 'invalid' cannot be used in a constant expression}}
2023-09-19 23:41:51 +02:00
}
struct S {
2023-09-27 18:41:11 +02:00
static constexpr int a = escalating ( 0 ) ; // expected-note 2{{in call to}}
// expected-error@-1 {{call to immediate function 'GH65985::escalating<int>' is not a constant expression}}
// expected-error@-2 {{constexpr variable 'a' must be initialized by a constant expression}}
2023-09-19 23:41:51 +02:00
} ;
}
2023-09-19 00:24:43 +02:00
namespace GH66324 {
consteval int allocate ( ) ; // expected-note 2{{declared here}}
struct _Vector_base {
int b = allocate ( ) ; / / expected - note 2 { { undefined function ' allocate ' cannot be used in a constant expression } } \
/ / expected - error { { call to consteval function ' GH66324 : : allocate ' is not a constant expression } } \
// expected-note {{declared here}}
} ;
template < typename >
struct vector : _Vector_base {
constexpr vector ( )
// expected-note@-1 {{'vector' is an immediate constructor because its body contains a call to a consteval function 'allocate' and that call is not a constant expression}}
: _Vector_base { } { } // expected-note {{in the default initializer of 'b'}}
} ;
vector < void > v { } ;
// expected-error@-1 {{call to immediate function 'GH66324::vector<void>::vector' is not a constant expression}}
// expected-note@-2 {{in call to 'vector()'}}
}
2024-02-21 20:53:44 +01:00
namespace GH82258 {
template < class R , class Pred >
constexpr auto none_of ( R & & r , Pred pred ) - > bool { return true ; }
struct info { int value ; } ;
consteval auto is_invalid ( info i ) - > bool { return false ; }
constexpr info types [ ] = { { 1 } , { 3 } , { 5 } } ;
static_assert ( none_of (
types ,
+ [ ] ( info i ) consteval {
return is_invalid ( i ) ;
}
) ) ;
static_assert ( none_of (
types ,
[ ] {
return is_invalid ;
} ( )
) ) ;
}
[Clang] Fix P2564 handling of variable initializers (#89565)
The following program produces a diagnostic in Clang and EDG, but
compiles correctly in GCC and MSVC:
```cpp
#include <vector>
consteval std::vector<int> fn() { return {1,2,3}; }
constexpr int a = fn()[1];
```
Clang's diagnostic is as follows:
```cpp
<source>:6:19: error: call to consteval function 'fn' is not a constant expression
6 | constexpr int a = fn()[1];
| ^
<source>:6:19: note: pointer to subobject of heap-allocated object is not a constant expression
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/14.0.1/../../../../include/c++/14.0.1/bits/allocator.h:193:31: note: heap allocation performed here
193 | return static_cast<_Tp*>(::operator new(__n));
| ^
1 error generated.
Compiler returned: 1
```
Based on my understanding of
[`[dcl.constexpr]/6`](https://eel.is/c++draft/dcl.constexpr#6):
> In any constexpr variable declaration, the full-expression of the
initialization shall be a constant expression
It seems to me that GCC and MSVC are correct: the initializer `fn()[1]`
does not evaluate to an lvalue referencing a heap-allocated value within
the `vector` returned by `fn()`; it evaluates to an lvalue-to-rvalue
conversion _from_ that heap-allocated value.
This PR turns out to be a bug fix on the implementation of
[P2564R3](https://wg21.link/p2564r3); as such, it only applies to C++23
and later. The core problem is that the definition of a
constant-initialized variable
([`[expr.const/2]`](https://eel.is/c++draft/expr.const#2)) is contingent
on whether the initializer can be evaluated as a constant expression:
> A variable or temporary object o is _constant-initialized_ if [...]
the full-expression of its initialization is a constant expression when
interpreted as a _constant-expression_, [...]
That can't be known until we've finished parsing the initializer, by
which time we've already added immediate invocations and consteval
references to the current expression evaluation context. This will have
the effect of evaluating said invocations as full expressions when the
context is popped, even if they're subexpressions of a larger constant
expression initializer. If, however, the variable _is_
constant-initialized, then its initializer is [manifestly
constant-evaluated](https://eel.is/c++draft/expr.const#20):
> An expression or conversion is _manifestly constant-evaluated_ if it
is [...] **the initializer of a variable that is usable in constant
expressions or has constant initialization** [...]
which in turn means that any subexpressions naming an immediate function
are in an [immediate function
context](https://eel.is/c++draft/expr.const#16):
> An expression or conversion is in an immediate function context if it
is potentially evaluated and either [...] it is a **subexpression of a
manifestly constant-evaluated expression** or conversion
and therefore _are not to be considered [immediate
invocations](https://eel.is/c++draft/expr.const#16) or
[immediate-escalating
expressions](https://eel.is/c++draft/expr.const#17) in the first place_:
> An invocation is an _immediate invocation_ if it is a
potentially-evaluated explicit or implicit invocation of an immediate
function and **is not in an immediate function context**.
> An expression or conversion is _immediate-escalating_ if **it is not
initially in an immediate function context** and [...]
The approach that I'm therefore proposing is:
1. Create a new expression evaluation context for _every_ variable
initializer (rather than only nonlocal ones).
2. Attach initializers to `VarDecl`s _prior_ to popping the expression
evaluation context / scope / etc. This sequences the determination of
whether the initializer is in an immediate function context _before_ any
contained immediate invocations are evaluated.
3. When popping an expression evaluation context, elide all evaluations
of constant invocations, and all checks for consteval references, if the
context is an immediate function context. Note that if it could be
ascertained that this was an immediate function context at parse-time,
we [would never have
registered](https://github.com/llvm/llvm-project/blob/760910ddb918d77e7632be1678f69909384d69ae/clang/lib/Sema/SemaExpr.cpp#L17799)
these immediate invocations or consteval references in the first place.
Most of the test changes previously made for this PR are now reverted
and passing as-is. The only test updates needed are now as follows:
- A few diagnostics in `consteval-cxx2a.cpp` are updated to reflect that
it is the `consteval tester::tester` constructor, not the more narrow
`make_name` function call, which fails to be evaluated as a constant
expression.
- The reclassification of `warn_impcast_integer_precision_constant` as a
compile-time diagnostic adds a (somewhat duplicative) warning when
attempting to define an enum constant using a narrowing conversion. It
also, however, retains the existing diagnostics which @erichkeane
(rightly) objected to being lost from an earlier revision of this PR.
---------
Co-authored-by: cor3ntin <corentinjabot@gmail.com>
2024-05-09 03:22:11 -04:00
# if __cplusplus >= 202302L
namespace lvalue_to_rvalue_init_from_heap {
struct S {
int * value ;
constexpr S ( int v ) : value ( new int { v } ) { } // expected-note 2 {{heap allocation performed here}}
constexpr ~ S ( ) { delete value ; }
} ;
consteval S fn ( ) { return S ( 5 ) ; }
int fn2 ( ) { return 2 ; } // expected-note {{declared here}}
constexpr int a = * fn ( ) . value ;
constinit int b = * fn ( ) . value ;
const int c = * fn ( ) . value ;
int d = * fn ( ) . value ;
constexpr int e = * fn ( ) . value + fn2 ( ) ; / / expected - error { { must be initialized by a constant expression } } \
/ / expected - error { { call to consteval function ' lvalue_to_rvalue_init_from_heap : : fn ' is not a constant expression } } \
/ / expected - note { { non - constexpr function ' fn2 ' } } \
// expected-note {{pointer to heap-allocated object}}
int f = * fn ( ) . value + fn2 ( ) ; / / expected - error { { call to consteval function ' lvalue_to_rvalue_init_from_heap : : fn ' is not a constant expression } } \
// expected-note {{pointer to heap-allocated object}}
}
# endif
2024-05-13 16:04:20 +02:00
# if __cplusplus >= 202302L
namespace GH91509 {
consteval int f ( int ) { return 0 ; }
template < typename T >
constexpr int g ( int x ) {
if consteval {
return f ( x ) ;
}
if ! consteval { }
else {
return f ( x ) ;
}
return 1 ;
}
int h ( int x ) {
return g < void > ( x ) ;
}
}
# endif
2024-05-23 17:53:07 +02:00
namespace GH91308 {
constexpr void f ( auto ) {
static_assert ( false ) ;
}
using R1 = decltype ( & f < int > ) ;
}