Thanks to Richard Smith for reporting the problem and providing a thorough analysis of the implementation divergence, and for providing helpful suggestions for the wording. Any remaining bugs in this paper are the author's alone, not Mr. Smith's.
This paper proposes to strike the restriction that a constexpr function cannot contain a definition of a variable of non-literal type (or of static or thread storage duration), or a goto statement, or an identifier label. The rationale is briefly that the mere presence of the aforementioned things in a function is not in and of itself problematic; we can allow them to be present, as long as constant evaluation doesn't evaluate them.
With regards to consistency with other language constructs, namely that they're not ill-formed in function definitions but are ill-formed if an attempt is made to constant-evaluate them, this proposal is in P0592 priority bucket 2, since it's an Improvement, a consistency bug fix.
However, this proposal is adding a capability that wasn't there before, so from that perspective, this is in P0592 priority bucket 3, an Addition.
The suggestion of the proposal author is to treat this as an Improvement rather than an Addition. We are making the language more regular here.
The problem was reported by Richard Smith on the reflectors. Mr. Smith provided the following testcase:
template<typename T> constexpr bool f() { if (std::is_constant_evaluated()) { // ... return true; } else { T t; // ... return true; } } struct nonliteral { nonliteral(); }; static_assert(f<nonliteral>());
As Mr. Smith also suggested, this is
According to tests on multiple compilers, there's implementation divergence; some accept the testcase, some reject it, and for some it depends on exactly how the testcase function body is written. While some of that may be due to imperfections in implementations, the standard is inconsistent here; the wording is probably clear as such, but the declaration special cases that remain in the specification seem undesirable.
We have allowed a constexpr function to contain a throw-expression since C++11, as long as constant evaluation doesn't evaluate it. We have since extended the set of allowed things to contain, for example, inline assembly. Removing the restriction of variable definitions seems perfectly in line with the general direction we've been going towards.
There's another even bigger and longer-term trend that this proposal follows. With this change, we move further into the direction of not diagnosing language constructs at declaration/definition time, but at the time of use. And if they're actually not used, there are no diagnostics.
Now, we could entertain providing just a small fix for this problem, and indeed just lift the requirement for variable definitions. But there are two restrictions right next to it in the standard, forbidding the presence of gotos and labels in a constexpr functions. These language constructs, however, are fine as long as they're not constant-evaluated. Thus the proposal here is to strike those restrictions while we're at it.
This proposal includes a bump of the value of __cpp_constexpr
.
While it would be fathomable that such a bump, or feature-detection in
general, wouldn't be needed because it's possible to refactor the uses
of nonliterals and thread_locals and statics into separate functions
and call those functions in constexpr functions, the arguments for
the detection are thus:
constexpr int f(int x) { if (std::is_constant_evaluated()) { return x; } else { #if __cpp_constexpr < 202103L int n = [&]() { #endif thread_local int n = x; #if __cpp_constexpr < 202103L return n; }(); #endif return n + x; } }
In [cpp.predefined]/[tab:cpp.predefined.ft], bump the value of __cpp_constexpr:
__cpp_constexpr201907L202103L
In [dcl.constexpr]/3, strike the last bullet:
its function-body shall not enclose
- a goto statement,
- an identifier label,
- a definition of a variable of non-literal type or of static or thread storage duration.
[Note 3: A function-body that is = delete or = default encloses none of the above. — end note]
Also in [dcl.constexpr]/3, tweak the example:
constexpr intfirstconstant_non_42(int n) { // OKstatic int value = n; // error: variable has static storage durationif (n == 42) { static int value = n; return value; } returnvaluen; }
In [dcl.constexpr]/6, amend the example:
struct D : B { constexpr D() : B(global) { } // ill-formed, no diagnostic required // lvalue-to-rvalue conversion on non-constant global constexpr int f(int x) { static int n = x; return n + x;} // ill-formed, no diagnostic required // all calls reach the static variable declaration
In [expr.const]/5, add new bullets in the beginning and at the end:
- this (7.5.2), except in a constexpr function (9.2.6) that is being evaluated as part of E;
- a control flow that passes through a declaration of a variable with static or thread storage duration;
- ...
- an asm-declaration (9.10);
or- an invocation of the va_arg macro ([cstdarg.syn])
.; or- a goto statement ([stmt.goto]).