1

I have the following infinitely recursive constexpr function:

constexpr int foo(int x) {
    return foo(x + 1);
}

Then I found that

int main() {
    static int x = foo(5);
    static int y = std::integral_constant<int, foo(5)>::value;
    static constinit int z = foo(5);
}

the compiler (GCC13, on Ubuntu 22.04) reports error on the initialization of y and z due to compile-time infinite recursion, but no error for x. If we remove the declarations for y and z and then run the program, Address Boundary Error is caused.

Here 5 is a constant expression, but the initialization of x with foo(5) is not performed in compile-time. Why?

What's more, I tried to modify foo:

constexpr int foo(int x) {
    if (std::is_constant_evaluated())
        return 42;
    return foo(x + 1);
}

Then I found that x is initialized to 42, with no compile-time or run-time infinite recursions, which indicates that it happens at compile-time this time.

So, is the initializer foo(5) for x evaluated at compile-time? Is this initialization static initialization or dynamic initialization? What are the actual rules about that?

Moreover, I'm not so familiar with constinit. The reason for initializing y with std::integral_constant is that we want to ensure static initialization. Can I say that writing constinit is always a safe and modern alternative to it?

4
  • "initialization of x with foo(5) is not performed in compile-time. Why...." constexpr doesn't guarantee/ensure that it must/will be evaluated at compile time. Commented May 18, 2024 at 10:46
  • Evaluation is done at compile-time depending of constexpr "context", not according to known argument. Commented May 18, 2024 at 11:51
  • If you want guarantee , use static constexpr. Commented May 18, 2024 at 15:02
  • @user12002570 But I don't want the variable to be constexpr. Commented May 18, 2024 at 16:42

1 Answer 1

3

The static int variable x is constant-initialized if and only if its initializer (foo(5)) is a constant expression, and foo(5) is a constant expression only if it doesn't have infinite recursion (for obvious reason). If x is not constant-initialized, then dynamic initialization is performed.

[expr.const]/2, emphasis mine:

A variable or temporary object o is constant-initialized if

  • either it has an initializer or its default-initialization results in some initialization being performed, and
  • the full-expression of its initialization is a constant expression when interpreted as a constant-expression, except that if o is an object, that full-expression may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types.
    [Note 2: Such a class can have a non-trivial destructor. Within this evaluation, std​::​is_constant_evaluated() ([meta.const.eval]) returns true. — end note]

[basic.start.static]/2:

Constant initialization is performed if a variable or temporary object with static or thread storage duration is constant-initialized ([expr.const]). [...] Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization.

And constinit is the right way to ensure static initialization.

[dcl.constinit]/2:

If a variable declared with the constinit specifier has dynamic initialization ([basic.start.dynamic]), the program is ill-formed, even if the implementation would perform that initialization as a static initialization ([basic.start.static]).

Sign up to request clarification or add additional context in comments.

1 Comment

Note that there is an open issue about whether static initialization should be deferred to runtime if an implementation limit is exceeded during the trial constant initialization: open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2776

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.