14

Since C++20 we can allocate memory during compile time and we have to free that during compile time. Therefore this raised some questions for me: first why does this work?

constexpr int* return_ptr() {
    return new int{ 1 };
}

void call_return_ptr(){
    int* int_ptr = return_ptr();
}

int main() {
    call_return_ptr();
    return 0;
}

I'm using the ptr returned from constexpr and not freeing that during compile time. This should be an error based on what I've understood. Secondly if the first example works then why this shouldn't work:

constexpr int* return_ptr() {
    return new int{ 1 };
}

void call_return_ptr(){
    constexpr int* int_ptr = return_ptr();
}

int main() {
    call_return_ptr();
    return 0;
}

and even if we try to delete the pointer during compile time like this:

constexpr int* return_ptr() {
    return new int{ 1 };
}

constexpr void call_return_ptr(){
    constexpr int* int_ptr = return_ptr();
    delete int_ptr;
}

int main() {
    call_return_ptr();
    return 0;
}

this is still an error. What is going on here?

---EDIT Nicol Bolas gave a very good and profound answer to first and somewhat second part of my question, but that still leaves why the third version (the one that call_return_ptr is also constexpr) isn't working. Based what's on the comments on Nicol Bolas's answer, it is still unclear to me, why even if we change the third version's function signature to consteval that still gives an error since everything in call_return_ptr should be in a constexpr context.

1
  • constexpr functions can be called both a compile-time and run-time, so the fact that the first example works doesn't imply that the second should. Commented Aug 14, 2021 at 20:11

1 Answer 1

17

There is no such thing as "compile-time". Not really.

What there is is "constant expression evaluation". These are a certain category of expressions whose evaluation happens in a certain way, such that the results of their evaluation are available to the source code.

A constexpr function is a function which may be evaluated during constant expression evaluation. If you call it outside of a constant expression context, then it does not undergo constant expression evaluation.

The initializer for a constexpr (or constinit) variable is a constant expression context. Therefore, it must undergo constant expression evaluation.

The rules for constant expression memory allocation only apply when the function is called within a constant expression context. So your first call_return_ptr calls return_ptr and stores the result in a runtime variable. This is not a constant expression context; it's just regular expression evaluation. The function allocates memory and returns a pointer, just like any other.

Your second version uses the result of that call to return_ptr to initialize a constexpr variable. This is a constant expression context... right up until the variable finishes initialization. See, you called call_return_ptr outside of a constant expression context, so the only part of that function which is a constant expression context is the initialization of the constexpr variable. Everything else, including the delete call, is not part of a constant expression context.

So unless you call this function within a constant expression context, you'll get a compile error since constant evaluation produced a pointer to constexpr allocated memory that was not deleted in that constant evaluation.

If you want to ensure that a particular function is always a constant expression context, it must be declared consteval.

The third one doesn't work for more or less the same reason: the function is not being called in a constant expression context. Indeed, a void function cannot itself be in a constant expression context unless it is consteval, since there is no result of a void function to use in constant evaluation.

Furthermore, by making the inner variable constexpr, you have created a constant expression context within the function. The thing is, the rules of constant evaluation are recursive. A constant expression context must not leak memory, even if that context is evaluated within another constant expression context. Each constant expression context has to follow those rules, regardless of the context outside of its evaluation.

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

9 Comments

Thank you for your time and your profound answer. I've read your answer and I couldn't figure out why the third version is an error. Because based on what you have said it is being called in a constexpr context and I'm freeing the memory, so why is there an error? I fully understood the problem with first and second versions though.
@ClassY: "Because based on what you have said it is being called in a constexpr context" No, it isn't. The expression calling the function does not use the return value to initialize a constexpr variable. Also, it's a void function, so the return value cannot initialize a constexpr variable. So the function call itself is not a constant expression context; only the initialization of the constexpr variable inside of the function is a constant expression context.
But why the compiler decides that the third version doesn't need to be a constexpr and removed it? And I also tried the third version again by changing the function signature from constexpr to consteval and it still failed!
@ClassY: Did you leave the variable constexpr? If so... why?
because I'm not going to change where the pointer is pointing to and since its already in a constexpr context the only meaning it has is applying const. The reasoning is besides the point I'm making, could you please help me on why that gives an error(third version with changes that we talked about in the comments)?
|

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.