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.
Related
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.
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.
Consider the following program:
template<typename T>
constexpr int f()
{
T{}.i; // error if instantiated with [T = double]
return 42;
}
constexpr void g(char);
using U = decltype( g( {f<double>()} ) );
To my understanding, the last line is an error because the call to f<double>() is within a brace initializer, and even though f<T> returns an int, the value of the returned int is needed to decide if it can be narrowed to a char as expected by g. This requires the definition of f to be instantiated with double, which causes an error. Both gcc and clang reject this code.
However, if the definition of g is changed to accept an int parameter:
constexpr void g(int);
then it seems that there is no need to instantiate the definition of f, since the narrowing conversion must succeed. Indeed, gcc accepts this, but clang still instantiates f with double and rejects the code. Additionally, if f is only declared, but not defined, clang accepts the code, which implies that the definition is not needed, and shouldn't be instantiated.
Is my reasoning correct, and this is a clang bug, or is instantiation required, and this is actually a gcc bug?
This is CWG #1581 I think, resolved by P0859.
temp.inst/5 says:
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.
Does the existence affect the semantics of the program?
temp.inst/8 says:
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.
Is it needed for constant evaluation by an expression?
expr.const/15.6-7 says:
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
a variable whose name appears as a potentially constant evaluated expression that is either a constexpr variable or is of non-volatile const-qualified integral type or of reference type.
Is it named by an expression that is potentially constant evaluated?
expr.const/15.1-15.5 says:
An expression or conversion is potentially constant evaluated if it is:
a manifestly constant-evaluated expression,
a potentially-evaluated expression,
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.
It is an immediate subexpression of a braced-init-list.
According to this answer apparently there is no good reason why structured bindings are not allowed to be constexpr, yet the standard still forbids it. In this case, however, shouldn't the use of the structured bindings inside the constexpr function also be prohibited? Consider a simple snippet:
#include <utility>
constexpr int foo(std::pair<int, int> p) {
auto [a, b] = p;
return a;
}
int main() {
constexpr int a = foo({1, 2});
static_assert(a == 1);
}
Both gcc and clang does not cause trouble compiling the code. Is the code ill-formed either way or is this one actually allowed?
In the case of function declaration, the constexpr specifier is an assertion made to the compiler that the function being declared may be evaluated in a constant expression, i.e. an expression that can be evaluated at compile-time. Nevertheless the object initialization inside a declaration does not need to have constexpr inside its declaration specifier to be a constant expression.
Shorter: constexpr function may imply constant expression but constant expression initialization does not need that the associated declaration has a constexpr specifier.
You can check this in the C++ standard [dcl.constexpr]:
A call to a constexpr function produces the same result as a call to an equivalent non-constexpr function in
all respects except that
— a call to a constexpr function can appear in a constant expression[...]
This is the evaluation of an expression that determines if an expression is a
constant expression [expr.const]:
An expression e is a core constant expression unless the evaluation of e [...] would evaluate one of the following expression[...]
A declaration is not an expression, so an initialization of an object being declared is a constant expression irrespective of the presence or not of a constexpr specifier in the declaration.
Finally, in [dcl.constexpr], it is specified that a constexpr function must be such that there exist parameters for which its body can be evaluated as a constant expression:
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 (8.20), or, for a constructor, a constant initializer for some object (6.6.2), the
program is ill-formed, no diagnostic required.
When you declare constexpr int a the compiler expects a to be inialized by a constant expression and the expression foo({1,2}) is a constant expression, so your code is well formed.
PS: Nevertheless, declaration specifiers (static, thread_local=>static) in the the declaration of function local variable implies that the function cannot be declared constexpr.
There are several requirements that a constexpr function must meet. There are some requirements for the body of a constexpr function, and the shown code does not appear to violate any of them. The key point is that there is no requirement that every statement in a function must be a constexpr. The only interesting requirement in question here, is this one:
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.
Note the last sentence. The compiler may, but is not required to, throw a red flag.
The key requirement is merely that there is some assortment of parameter values to the function that results in a constant result from the function (and the function body meets the listed requirements). For example, the function might use a structured binding conditionally; but for some set of parameter values do something else, producing a constant result. This would tick this checkbox for a constexpr function.
But, despite the sophistication of modern C++ compilers, they may not necessarily be capable of reaching this determination in every possible instance, so, in practice, it would be hard to enforce such a requirement, hence the compilers are permitted to just take this for granted.
#include <exception>
constexpr bool foo(bool x)
{
return x ? true : throw std::exception();
}
int main()
{
// 1) must never be compiled
// static_assert(foo(false), "");
// 2) must always be compiled?
const bool x = foo(false);
// 3) must never compile?
constexpr bool y = foo(false);
return 0;
}
I'm sure that (1) must lead to a compile error. I'm quite sure that (2) must not be rejected at compile time, though it will fail at runtime.
The interesting case is the constexpr variable (3). In this simple example, gcc and clang actually evaluate the expression, and will therefore reject the program. (Error message: y is not a constant expression).
Is every C++11 compiler forced to reject the program? What if foo(false) was replaced by a more complex expression?
I was suprised to find out that constexpr were not turing-complete, though it will be after a change in the specification:
Is constexpr-based computation Turing complete?
Maybe this is related to my question. As far as I understand, the compiler is allowed to postpone the actual evaluation of the constexpr (3) in this example until runtime. But if constexpr are turing-complete, I find it hard to believe that the compiler can decide for all constexpr whether an exception will be thrown (which means that the constexpr is invalid).
By my reading, yes, every compiler must complain about statement (3).
N3242 7.1.5 paragraph 9:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. If it initialized by a constructor call, that call shall be a constant expression (5.19). Otherwise, every full-expression that appears in its initializer shall be a constant expression. Each implicit conversion used in converting the initializer expressions and each constructor call used for the initialization shall be one of those allowed in a constant expression (5.19).
I think of a constexpr object as "evaluated at compile time", and a constexpr function or constexpr constructor as "might be evaluated at compile time". A compiler must determine the semantic validity of statements like (3) at compile time. You could argue that the "evaluation" can still be done at run time, but checking for validity does most of that work anyway. Plus, the code could then continue to instantiate a template like Check<y>, which pretty much guarantees the compiler needs to figure out the value of y at compile-time.
This does mean you could write a diabolical program to make the compiler take a really long or infinite time. But I suspect that was already possible with operator-> tricks.
I'm sure that (1) must lead to a compile error. I'm quite sure that (2) must not be rejected at compile time, though it will fail at runtime.
Correct. The throw part of the conditional operator is not a constant expression, and in (1), it's not unevaluated. For (2), foo is not forced to be evaluated at compile-time.
For (3), how would the compiler be allowed to post-pone evaluation? The constexpr decl-specifier forces foo to be evaluated at compile-time. It's basically the same as (1), initialization of y is a context where a constant expression is required.
§7.1.6 [dcl.constexpr] p9
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. If it is initialized by a constructor call, that call shall be a constant expression (5.19). Otherwise, or if a constexpr specifier is used in a reference declaration, every full-expression that appears in its initializer shall be a constant expression. [...]