In C++17, are you allowed to modify global variables in a constexpr function?
#include <iostream>
int global = 0;
constexpr int Foo(bool arg) {
if (arg) {
return 1;
}
return global++;
}
int main() {
std::cout << global;
Foo(true);
std::cout << global;
Foo(false);
std::cout << global;
}
I wouldn't expect you to be able to, but clang 6 allows it: https://godbolt.org/g/UB8iK2
GCC, however, doesn't: https://godbolt.org/g/ykAJMA
Which compiler is correct?
Which compiler is correct?
Clang is right.
The definition of a constexpr function as per dcl.constexpr/3
The definition of a constexpr function shall satisfy the following
requirements:
(3.1) its return type shall be a literal type;
(3.2) each of its parameter types shall be a literal type;
(3.3) its function-body shall be = delete, = default, or a compound-statement
that does not contain:
(3.3.1) an asm-definition,
(3.3.2) a goto statement,
(3.3.3) an identifier label,
(3.3.4) a try-block, or
(3.3.5) a definition of a variable of non-literal type or of static or
thread storage duration or for which no initialization is performed.
Also as per dcl.constexpr/5:
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,
Foo(true) could be evaluated to a core constant expression (i.e 1).
Also, Foo(false) could be but is not required to be constant evaluated.
CONCLUSION
Thus, a bug in GCC.
Many thanks to #Barry, #aschepler and #BenVoigt for helping me with this answer.
I'll add that dcl.constexpr/5 additionally requires:
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 ([basic.start.static]), the program is ill-formed, no diagnostic required.
Since you deiberately wrote the function so that Foo(true) evaluates to a core constant expression, Foo(false) is not required to.
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 {
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.
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.
For example the code below doesn't compile unless incr() is declared constexpr:
int incr(int& n) {
return ++n;
}
constexpr int foo() {
int n = 0;
incr(n);
return n;
}
Looking at §7.1.5/3 in C++14 we have:
The definition of a constexpr function shall satisfy the following
constraints:
(3.1) — it shall not be virtual (10.3);
(3.2) — its return type shall be a literal type;
(3.3) — each of its parameter types shall be a literal type;
(3.4) — its function-body shall be = delete, = default, or a compound-statement that does not contain
(3.4.1) — an asm-definition,
(3.4.2) — a goto statement,
(3.4.3) — a try-block, or
(3.4.4) — a definition of a variable of
non-literal type or of static or thread storage duration or for which
no initialization is performed.
Two paragraphs later, in [dcl.constexpr]/5:
For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting
constexpr constructor, if no argument values exist such that an invocation of the function or constructor
could be an evaluated subexpression of a core constant expression (5.20), or, for a constructor, a constant
initializer for some object (3.6.2), the program is ill-formed; no diagnostic required.
No argument exists such that foo() could be a core constant expression because of incr(), therefore the program is ill-formed (NDR).
What you're looking for is § 5.19:
A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (1.9), would evaluate one of the following expressions:
This applies to the evaluation of an expression that is a constexpr function call. That is, calling a constexpr function will be a 'core constant expression' if evaluating the function, i.e. executing the body of the function according to the rules of the C++ abstract machine, doesn't do any of the things forbidden in the list given in § 5.19.
One of items in the list is:
an invocation of a function other than [...] a constexpr function
So to apply this to your example: evaluating the expression foo() evaluates a call to a function incr() which is not a constexpr function, which means that the expression foo() is not a core constant expression.
Further, since the above is true for all possible invocations of your function foo, the rule in § 7.1.5/5 kicks in and means that your example program is ill-formed, no diagnostic required, even if you never actually call foo().
As Ben Voigt points out a constexpr function can contain calls to non-consexpr functions, so long as the particular evaluation of the function does not actually evaluate any such function call (or it appears in a context that does not require a constant expression).
The restrictions in 5.19 only pertain to what expressions actually end up being evaluated as part of the evaluation of an expression.
For example:
#include <iostream>
int incr(int &n) { return ++n; }
enum E {be_constexpr, not_constexpr};
constexpr int foo(E e = be_constexpr) {
int n = 0;
if (e == not_constexpr) { incr(n); }
return n;
}
int main() {
constexpr int a = foo(); // foo() is a constant expression
int b = foo(not_constexpr); // may or may not evaluate `foo(non_constexpr)` at runtime. In practice modern C++ compilers will do compile-time evaluation here even though they aren't required to.
// constexpr int c = foo(not_constexpr); // Compile error because foo(not_constexpr) is not formally a constant expression, even though modern compilers can evaluate it at compile-time.
std::cout << a << ' ' << b << '\n';
}
It doesn't.
The following is allowed, even though it does exactly what you surmise is forbidden:
int incr(int& n) {
return ++n;
}
constexpr int foo(bool x) {
int n = 0;
if (x) incr(n);
return n;
}
The code in your question is disallowed by the rule with Barry quoted in his answer. But in my variation, there does exist a set of parameters (specifically, false) with which invocation of foo results in a compile-time constant expression.
Note that a diagnostic isn't required -- a conforming compiler could allow your version to compile as well.
I got this example from §5.19/2 in N4140:
constexpr int incr(int &n) {
return ++n;
}
As far as I can tell, this is not a constexpr function. But the snippet compiles in clang and g++. See live example. What am I missing here?
In C++14 the rules for constexpr function were relaxed and the paper N3597: Relaxing constraints on constexpr functions. The paper goes into the rationale and the effects and it includes the following (emphasis mine):
As in C++11, the constexpr keyword is used to mark functions which the implementation is required to evaluate during translation, if they are used from a context where a constant expression is required. Any valid C++ code is permitted in constexpr functions, including the creation and modification of local variables, and almost all statements, with the restriction that it must be possible for a constexpr function to be used from within a constant expression. A constant expression may still have side-effects which are local to the evaluation and its result.
and:
A handful of syntactic restrictions on constexpr functions are
retained:
asm-declarations are not permitted.
try-blocks and function-try-blocks are not permitted.
Declarations of variables with static and thread storage duration have some restrictions (see below).
and we can find this covered in N4140 section 7.1.5 [dcl.constexpr] which says:
The definition of a constexpr function shall satisfy the following constraints:
it shall not be virtual (10.3);
its return type shall be a literal type;
each of its parameter types shall be a literal type;
its function-body shall be = delete, = default, or a compound-statement that does not contain
an asm-definition,
a goto statement,
a try-block, or
a definition of a variable of non-literal type or of static or thread storage duration or for which
no initialization is performed.
The last example shows how incr can be used in a constexpr:
constexpr int h(int k) {
int x = incr(k); // OK: incr(k) is not required to be a core
// constant expression
return x;
}
constexpr int y = h(1); // OK: initializes y with the value 2
// h(1) is a core constant expression because
// the lifetime of k begins inside h(1)
and the rule that covers the lifetime of k begins inside h(1) is:
modification of an object (5.17, 5.2.6, 5.3.2) 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;
The wording in 7.1.5 [dcl.constexpr] shows us why incr is a valid constexpr:
For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting
constexpr constructor, if no argument values exist such that an invocation of the function or constructor
could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no
diagnostic required.
As the modified example given by T.C.:
constexpr int& as_lvalue(int&& i){ return i; }
constexpr int x = incr(as_lvalue(1)) ;
shows, we can indeed use incr as a subexpression of a core constant expression and therefore it is not ill-formed.
As far as I can tell, this is not a constexpr function.
Why do you say that?
The example from §5.19/2 reads:
constexpr int g(int k) {
constexpr int x = incr(k); // error: incr(k) is not a core constant
// expression because lifetime of k
// began outside the expression incr(k)
return x;
}
incr(k) not being a core constant expression does not mean incr cannot not be a constexpr function.
Under C++14's constexpr rules, it is possible to use incr in a constexpr context, for example:
constexpr int incr(int& n) {
return ++n;
}
constexpr int foo() {
int n = 0;
incr(n);
return n;
}
Unless it's downright impossible for the body of the function to be constexpr (for example, calling a non-constexpr function unconditionally), the compiler has no reason to produce an error at the point of definition.
A constexpr function may even contain paths/branches in the body which would not be constexpr. As long as they are never taken in a constexpr context, you will not get an error.
For example:
constexpr int maybe_constexpr(bool choice, const int& a, const int& b) {
return choice ? a : b;
}
constexpr int a = 0;
int b = 1;
static_assert(maybe_constexpr(true, a, b) == 0, "!");
live example