struct A {
~A() {}
consteval A() {}
consteval auto f() {}
};
int main() {
A{};
//A{}.f(); //1
}
https://godbolt.org/z/4KPY5P7o7
This program is accepted by ICC, GCC and Clang, but rejected by MSVC which complains that the destructor is not constexpr in the immediate function invocation.
Adding the line marked //1 results in all four compilers rejecting the code.
Question: In either case, are the compilers correct, and if so why?
Note that the interesting part here is that A is non-literal due to the non-constexpr non-trivial destructor. Removing its declaration, all compilers accept both the variant with and without //1.
There are a few restrictions specific to non-literal types for constexpr/consteval functions and for constant expressions, but I don't think any of them should apply here. The restrictions are on return types, parameter types, types of local variable definitions, rvalue-to-lvalue conversions and modifications of objects. I think only the last one can apply here. But what exactly does modification in [expr.const]/5.16 mean and which object would be modified here?
I also think MSVC's complaint is incorrect since the destruction of the object shouldn't be part of its constructor's immediate invocation.
See also my earlier question inspiring this one: Consteval constructor and member function calls in constexpr functions
Updated with more exact references to the standard:
The pieces I found relevant (links are from the N4868 draft found here):
An immediate invocation is a full-expression [expr.const]
"Temporary objects are destroyed as the last step in evaluating the full-expression ([intro.execution]) that (lexically) contains the point where they were created. ... The value computations and side effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression." [class.temporary]
"The argument list is the expression-list in the call augmented by the addition of the left operand of the . operator in the normalized member function call as the implied object argument ([over.match.funcs])." [over.call.func]
"A constant expression is either a glvalue core constant expression that ..., or a prvalue core constant expression whose ..." [expr.const]
"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 a non-constexpr function;" [expr.const]
"An immediate invocation shall be a constant expression." [expr.const]
"An object or reference is usable in constant expressions if it is ... a temporary object of non-volatile const-qualified literal type whose lifetime is extended ([class.temporary]) to that of a variable that is usable in constant expressions," [expr.const]
"A type is a literal type if it is: ... a possibly cv-qualified class type that has all of the following properties: it has a constexpr destructor ([dcl.constexpr])," [basic.types]
Consider the following example:
struct A {
~A() {} // not constexpr
consteval int f() { return 1; }
};
template<class T>
consteval int f(T&& a) { return sizeof(a); }
consteval int f(int x) { return x; }
void g() {}
int main() {
A a;
f(a); // ok
a.f(); // ok
f(a.f()); // ok
f(sizeof(A{})); // ok
f(A{}); // not ok TYPE 1 (msvc) or TYPE 2 (clang, gcc)
A{}.f(); // not ok TYPE 1 (msvc) or TYPE 2 (clang, gcc)
f((A{},2)); // not ok TYPE 1 (clang, msvc) or TYPE 2 (gcc)
f((g(),2)); // not ok TYPE 1 (clang, gcc, icc, msvc)
}
Error diagnostics are about violating that immediate invocations should be constant expressions.
// msvc:
error C7595: 'f' ((or 'A::f')): call to immediate function is not a constant expression
// icc:
call to consteval function "f(T&&) [with T=A]" ((or "A::f" or "f(int)")) did not produce a valid constant expression
// clang:
error: call to consteval function 'f<A>' ((or 'A::f' or 'f')) is not a constant expression
Note that gcc does not mention the violation of this consteval/immediate function specific rule explicitly.
For the temporaries we receive two types of diagnostics from different compilers.
Some see the problem in calling a non-constexpr destructor or function in a constant (full-)expression. TYPE 1:
// msvc:
note: failure was caused by call of undefined function or one not declared 'constexpr'
note: see usage of 'A::~A' ((or 'g'))
// icc:
note: cannot call non-constexpr function "g"
// gcc:
error: call to non-'constexpr' function 'void g()'
// clang:
note: non-constexpr function '~A' ((or 'g')) cannot be used in a constant expression
Others (except for icc, which is silent about it) highlight that non-literal type temporaries cannot be present in constant expressions. TYPE 2:
// gcc:
error: temporary of non-literal type 'A' in a constant expression
note: 'A' is not literal because:
note: 'A' does not have 'constexpr' destructor
// clang:
note: non-literal type 'A' cannot be used in a constant expression
I think for consteval consideration A{}.f() is equivalent to the f(A{}) case because of the implicit object parameter of A::f.
The surprising observation from Fedor that icc compiles A{A{}}.f() is true even if A::A(const A&) is implemented to call e.g. printf. The code compiles, but outputs nothing. I consider that a bug.
Interestingly icc generates an error for the semantically very similar f(A{A{}}) variant.
My original post for reference (helps understanding some of the comments) :
For me the output diagnostics make sense.
My mental model about immediate invocations is this: you are allowed to use an immediate function only within immediate contexts.
An expression that contains anything else than constexpr operations is not an immediate context.
In your example the expression is not only an invocation of the constexpr constructor, but because the temporary is part of the expression, its destruction should also happen as part of the evaluation of the expression. Therefore your expression is no longer an immediate context.
I was playing around just calling the constructor with placement new to avoid the dtor call being part of the expression, but placement new itself is not considered constexpr either. Which is, I think, conceptually best explained by pointers should not present in immediate contexts at all.
If you remove ctor/dtor from the expression:
A a;
a.f();
then it compiles fine.
An interesting bug in ICC that it fails to compile A{}.f() even with a constexpr dtor, and you cannot convince it no matter how trivial definition your f has:
error: call to consteval function "A::f" did not produce a valid constant expression
A{}.f();
^
while it compiles the simple a.f() variant listed above without any complaint.
Related
In the following code struct A has immediate function default constructor, and an object of the struct is created in the dynamic memory be means of new A{}:
struct A {
consteval A() {}
};
int main() {
new A{};
}
Only Clang accepts it.
GCC complains
error: the value of '<anonymous>' is not usable in a constant expression
6 | new A{};
| ^
note: '<anonymous>' was not declared 'constexpr'
And MSVC does as well:
error C7595: 'A::A': call to immediate function is not a constant expression
Demo: https://gcc.godbolt.org/z/6Px5WYGzd
Which compiler is right here?
Which compiler is right here?
Invoking a consteval constructor with new is ill-formed.
MSVC and GCC are right to reject it; clang is wrong as a diagnostic is required.
struct A { consteval A() {} };
consteval makes A::A() an immediate function1.
An immediate function can only be called from2,3:
another immediate function, or
a consteval if statement, or
a constant expression4.
new A{} is none of the above.
1) [dcl.constexpr]/2
A constexpr or consteval specifier used in the declaration of a function declares that function to be a constexpr function.
A function or constructor declared with the consteval specifier is called an immediate function.
2) [expr.prim.id.general]/4
A potentially-evaluated id-expression that denotes an immediate function shall appear only
(4.1) as a subexpression of an immediate invocation, or
(4.2) in an immediate function context.
3) [expr.const]/13
An expression or conversion is in an immediate function context if it is potentially evaluated and either:
(13.1) its innermost enclosing non-block scope is a function parameter scope of an immediate function, or
(13.2) its enclosing statement is enclosed ([stmt.pre]) by the compound-statement of a consteval if statement ([stmt.if]).
An expression or conversion 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 immediate invocation shall be a constant expression.
4) [expr.const]/11.2
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints:
(11.2) if the value is of pointer type, it contains the address of an object with static storage duration, the address past the end of such an object ([expr.add]), the address of a non-immediate function, or a null pointer value,
struct A {
consteval A() {};
};
constexpr bool g() {
auto a = new A;
delete a;
return true;
}
int main() {
static_assert(g());
}
https://godbolt.org/z/jsq35WxKs
GCC and MSVC reject the program, ICC and Clang accept it:
///MSVC:
<source>(6): error C7595: 'A::A': call to immediate function is not a constant expression
Compiler returned: 2
//GCC:
<source>: In function 'constexpr bool g()':
<source>:6:18: error: the value of '<anonymous>' is not usable in a constant expression
6 | auto a = new A;
| ^
<source>:6:18: note: '<anonymous>' was not declared 'constexpr'
<source>:7:12: error: type '<type error>' argument given to 'delete', expected pointer
7 | delete a;
| ^
Compiler returned: 1
Although, replacing new A by new A() results in GCC accepting the program as well (but not for new A{} either).
Making at least one of the following changes results in all four compilers accepting the program:
Replace consteval with constexpr
Replace constexpr with consteval
Replace
auto a = new A;
delete a;
with
auto alloc = std::allocator<A>{};
auto a = alloc.allocate(1);
std::construct_at(a);
std::destroy_at(a);
alloc.deallocate(a, 1);
with A a;, with auto&& a = A{}; or with A{};
Only exceptions:
Clang trunk with libstdc++ seems to fail compilation with the std::allocator version seemingly due to an unrelated bug. With Clang 13 or libc++ it is accepted as well.
In file included from <source>:1:
In file included from [...]/memory:78:
[...]/shared_ptr_atomic.h:459:14: error: missing 'typename' prior to dependent type name '_Atomic_count::pointer'
static _Atomic_count::pointer
MSVC rejects the std::allocator version as long as there is consteval on the constructor:
error C7595: 'A::A': call to immediate function is not a constant expression
<source>(10): note: see reference to function template instantiation '_Ty *std::construct_at<_Ty,,void>(_Ty *const ) noexcept(false)' being compiled
with
[
_Ty=A
]
Replacing static_assert(g()); with g() or removing the call completely does not seem to have any impact on these results.
Which compilers are correct and if the original is ill-formed, why is only that particular combination of qualifiers and construction method disallowed?
Motivated by the comments under this answer.
The relevant wording is [expr.const]/13:
An expression or conversion 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 immediate invocation shall be a constant expression.
Note the words 'or conversion' and 'implicit invocation' - this seems to imply that the rule is intended to apply on a per-function-call basis.1 The evaluation of a single atomic expression can consist of multiple such calls, as in the case of e.g. the new-expression which may call an allocation function, a constructor, and a deallocation function. If the selected constructor is consteval, the part of the evaluation of the new-expression that initializes the object (i.e. the constructor call), and only that part, is an immediate invocation. Under this interpretation, using new with a consteval constructor should not be ill-formed regardless of context - even outside of a constant expression - as long as the initialization of the object is itself constant, of course.
There is an issue with this reading, however: the last sentence clearly says that an immediate invocation must be an expression. A 'sub-atomic call' as described above isn't one, it does not have a value category, and could not possibly satisfy the definition of a constant expression ([expr.const]/11):
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints [...]
A literal interpretation of this wording would preclude any use of a consteval constructor outside of an immediate function context, since a call to it can never appear as a standalone expression. This is clearly not the intended meaning - among other things, it would render parts of the standard library unusable.
A more optimistic (but also less faithful to the words as written) version of this reading is that the atomic expression containing the call (formally: the expression which the call is an immediate subexpression of 2) must be a constant expression. This still doesn't allow your new A construct because it is not a constant expression by itself, and also leaves some uncertainty in cases like initialization of function parameters or variables in general.
I'm inclined to believe that the first reading is the intended one, and that new A should be fine, but clearly there's implementation divergence.
As for the contradictory 'shall be a constant expression' requirement, this isn't the only place in the standard where it appears like this. Earlier in the same section, [expr.const]/2.2:
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 [...]
Clearly, the following is supposed to be valid:
constinit A a;
But there's no constant expression in sight.
So, to answer your question:
Whether the call to g is being evaluated as part of a manifestly constant-evaluated expression does not matter3 regardless of which interpretation of [expr.const]/13 you go with. new A is either well-formed even during normal evaluation or ill-formed anywhere outside of an immediate function context.
By the looks of it, Clang and ICC implement the former set of rules while GCC and MSVC adhere to the latter. With the exception of GCC accepting new A() as an outlier (which is clearly a bug), neither are wrong, the wording is just defective.
[1] CWG2410 fixes the wording to properly include things like constructor calls (which are neither expressions nor conversions).
[2] Yes, a non-expression can be a subexpression.
[3] Such a requirement would be impossible to enforce.
struct A {
int i;
consteval A() { i = 2; };
consteval void f() { i = 3; }
};
constexpr bool g() {
A a;
a.f();
return true;
}
int main() {
static_assert(g());
}
https://godbolt.org/z/hafcab7Ga
The program is rejected by all of GCC, Clang, MSVC and ICC and replacing constexpr on g by consteval results in all four accepting it.
However, when removing the call a.f();, still with constexpr on g, only ICC still rejects the code. The other three now accept it.
I don't understand why this is the case. My understanding is that without consteval on g, the expression a.f() is not in an immediate function context, which will cause the member function call itself to be evaluated as separate constant expression, which then cannot modify the i member because the member's lifetime didn't begin during evaluation of that constant expression.
But why can the constructor perform the same operation on the same object, in the same context? Is a's lifetime considered to have begun during the evaluation of the consteval constructor?
Also note that the presence of the static_assert doesn't affect these results. Removing in addition constexpr completely from g then also doesn't change anything about the compiler behavior.
As noted by #Enlico, even replacing both A a; and a.f(); by A{}.f(); with constexpr on g results in all compilers except ICC accepting the code, although by my understanding, this expression should result in evaluation of two separate constant expressions for the immediate constructor invocation and for the immediate member function invocation. I think the latter call should behave exactly as a.f();, making this even more confusing.
(After reading #Barry's answer, I realize now that the last sentence didn't make any sense. Correction: A{} would be one constant expression for the constructor immediate invocation and A{}.f() as a whole would be the second constant expression for the member function immediate invocation. This is clearly different from the expression a.f().)
The rule is, from [expr.const]/13:
An expression or conversion is in an immediate function context if it is potentially evaluated and its innermost non-block scope is a function parameter scope of an immediate function. An expression or conversion 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 immediate invocation shall be a constant expression.
Where, an immediate function is simply the term for (from [dcl.constexpr]/2):
A function or constructor declared with the consteval specifier is called an immediate function.
From the example:
struct A {
int i;
consteval A() { i = 2; };
consteval void f() { i = 3; }
};
constexpr bool g() {
A a;
a.f();
return true;
}
The call a.f() is an immediate invocation (we're calling an immediate function and we're not in an immediate function context, g is constexpr not consteval), so it must be a constant expression.
It, by itself, must be a constant expression. Not the whole invocation of g(), just a.f().
Is it? No. a.f() mutates a by writing into a.i, which violates [expr.const]/5.16. One of the restrictions on being a constant expression is that you cannot have:
a modification of an object ([expr.ass], [expr.post.incr], [expr.pre.incr]) unless it is applied to a non-volatile lvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
Our object, a.i, didn't begin its lifetime within the evaluation of this expression. Hence, a.f() isn't a constant expression so all the compilers are correct to reject.
It was noted that A().f(); would be fine because now we hit the exception there - A() began its lifetime during the evaluation of this expression, so A().i did as well, hence assigning to it is fine.
You can think of this as meaning that A() is "known" to the constant evaluator, which means that doing A().i = 3; is totally fine. Meanwhile, a was unknown - so we can't do a.i = 3; because we don't know what a is.
If g() were a consteval function, the a.f() would no longer be an immediate invocation, and thus we would no longer require that it be a constant expression in of itself. The only requirement now is that g() is a constant expression.
And, when evaluating g() as a constant expression, the declaration of A a; is now within the evaluation of the expression, so a.f() does not prevent g() from being a constant expression.
The difference in rules arises because consteval functions need to be only invoked during compile time, and constexpr functions can still be invoked at runtime.
The following program
template<class T>
consteval auto foo(const T&) {
return 0;
}
template<class T>
consteval auto bar(const T& t) {
auto n = foo(t);
return n;
}
int main() {
static_assert(foo("abc") == 0);
static_assert(bar("abc") == 0);
}
is built fine in GCC, but Clang rejects it with the messages:
error: call to consteval function 'foo<char[4]>' is not a constant expression
note: in instantiation of function template specialization 'bar<char[4]>' requested here
static_assert(bar("abc") == 0);
note: function parameter 't' with unknown value cannot be used in a constant expression
auto n = foo(t);
Demo: https://gcc.godbolt.org/z/M6GPnYdqb
Is it some bug in Clang?
This is a clang bug. gcc and msvc are correct to accept it.
There are two relevant rules in question:
All immediate invocations must be constant expressions. This comes from [expr.const]/13:
An expression or conversion is in an immediate function context if it is potentially evaluated and either:
its innermost enclosing non-block scope is a function parameter scope of an immediate function, or
its enclosing statement is enclosed ([stmt.pre]) by the compound-statement of a consteval if statement ([stmt.if]).
An expression or conversion 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 immediate invocation shall be a constant expression.
And touching an unknown reference is not allowed in constant expressions (this is [expr.const]/5.13):
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 id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is usable in constant expressions or
its lifetime began within the evaluation of E;
For more on this latter rule, see my post on the the constexpr array size problem and my proposal to resolve this (hopefully for C++23).
Okay, back to the problem. foo is obviously fine, it doesn't do anything.
In bar, we call foo(t). This is not a constant expression (because t is an unknown reference), but we are in an immediate function context (because bar is consteval), so it doesn't matter that foo(t) is not a constant expression. All that matters is that bar("abc") is a constant expression (since that is an immediate invocation), and there's no rule we're violating there. It is pretty subtle, but the reference t here does have its lifetime begin within the evaluation of E -- since E here is the call bar("abc"), not the call foo(t).
If you mark bar constexpr instead of consteval, then the foo(t) call inside of it becomes an immediate invocation, and now the fact that it is not a constant expression is relevant. All three compilers correctly reject in this case.
On this site, it is specified that:
"A constexpr function must satisfy the following requirements:
[...]
there exists at least one set of argument values such that an invocation of the function could be an evaluated subexpression of a core constant expression (for constructors, use in a constant initializer is sufficient) (since C++14). No diagnostic is required for a violation of this bullet."
What is the meaning of the bolded statement?
Looking at the linked defect report
struct X {
std::unique_ptr<int> p;
constexpr X() { }
};
Before C++14, this would be ill-formed due to [dcl.constexpr]
For a constexpr constructor, if no argument values exist such that after function invocation substitution, every constructor call and full-expression in the mem-initializers would be a constant expression (including conversions), the program is ill-formed; no diagnostic required.
Which mandates that there exists some argument (in this case, only the empty set) that can create a constant expression for the invocation of X::X, as in
constexpr X x; // must be valid before C++14
Since std::unique_ptr isn't a literal type, it has a non-trivial destructor, this is impossible. Yet the defect report proposed that constexpr constructors should still be well-formed in such cases due to this kind of use case
X x; // not constexpr, but initialization should be constant
Hence the rewording
For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression, or, for a constructor, a constant initializer for some object , the program is ill-formed, no diagnostic required.
Translated, it means: a constexpr constructor is well-formed as long as it is a constexpr function, and its member initializations are also constexpr functions, even if the type itself can never be constexpr.