3

In C++20, we have the consteval keyword that declares an immediate function. For example:

consteval int f(int x) { return x * x; }

Such a function is required to produce a constant expression. However, according to the standard, a constant expression is not required to be actually evaluated at compilation time, unless it is used in a place where a constant is required, such as in a template parameter. For example, nothing in the standard seems to require that this is evaluated at compilation time:

int a = f(10);

However, the requirements strongly suggest that an immediate function is meant to be evaluated at compilation time.

It also appears that nothing in the standard requires that a constexpr variable be evaluated at compilation time. So even if we make the variable constexpr, i.e.

constexpr int a = f(10);

it only asserts that f(10) is a constant expression and makes a a constant expression (but again nothing about constant expressions require that they are actually evaluated at compilation time). However, just like before, the requirements for constexpr variables strongly suggest that they are meant to be evaluated at compilation time.

Only constinit variables are defined differently - constinit variables are required to have static initialization, so they must be calculated at compilation time and embedded directly in the binary. However, this answer to a related question says that constexpr implies constinit, at least for global variables, which seems to contradict what I've written above.

So, are consteval functions and constexpr variables guaranteed to be evaluated at compilation time?


Side note: My actual use case involves trying to initialize a field in a struct with a constant, like this:

consteval int my_complicated_calculation() {
    // do complicated mathematics and return an int
}
struct A {
    int value;
    A() : value{my_complicated_calculation()} {}
}
Bernard
  • 5,209
  • 1
  • 34
  • 64
  • Maybe put something in there that can't be done at compile time and see if it fails to compile? That would tell you for sure if it is guaranteed. – xaxxon Aug 02 '22 at 17:52
  • 2
    Jason Turner did some shows about this. E.g [this one](https://youtu.be/UdwdJWQ5o78). Also check the shows before and after it – JHBonarius Aug 02 '22 at 17:53
  • @xaxxon That's not going to work, because a constant expression is required to be _possible_ to evaluate at compilation time, so by definition it's not going to compile if you put something that can't be done at compilation time. But that won't answer my question. My question is about whether it's actually _guaranteed_ to be evaluated at compilation time, not whether it's _possible_ to do so. – Bernard Aug 02 '22 at 17:54
  • 1
    I believe consteval cannot have runtime code generated. – apple apple Aug 02 '22 at 17:54
  • @Bernard if you can write a function that can't be evaluated at compilation time but it compiles then you know it's not guaranteed to be evaluated at compile time because if it were, it must not compile. A guarantee of any sort would force it to fail to compile. The failure to compile is the only possible guarantee of what you're asking for. – xaxxon Aug 02 '22 at 18:00
  • 4
    As always, the question is, can you write a standard-conforming program that can tell the difference? – Pete Becker Aug 02 '22 at 18:01
  • @xaxxon But that logic can't be used to prove the inverse - if every function that can't be evaluated at compilation time does not compile, I would only know that it must be evaluatable at compilation time, not whether it will actually be evaluated at compilation time. – Bernard Aug 02 '22 at 18:05
  • @PeteBecker Well, `constinit` sure does guarantee initialization at compilation time, so certainly it can be written into the standard. – Bernard Aug 02 '22 at 18:07
  • @bernard if there is no guarantee that it must be evaluatable at compile time then there can be no guarantee that it is. So it's possible that the test answers your question and it's trivial, so I'd suggest trying it. – xaxxon Aug 02 '22 at 18:48
  • 1
    *"constinit sure does guarantee initialization at compilation time"* Does it? – HolyBlackCat Aug 02 '22 at 19:22
  • 1
    @Bernard -- "sure does guarantee" -- can you show some code whose behavior depends on whether an object marked `constinit` is initialized at "compilation time"? Hint: first you have to rigorously define "compilation time" with reference to actual requirements in the C++ standard. Sure, we all know what "compilation time" means, but that doesn't make it a term of art in the standard. – Pete Becker Aug 02 '22 at 20:27
  • @Bernard The only thing the standard says about `constinit` is that it guarantees no dynamic initialization and the only thing it says about static initialization's (i.e. not dynamic initialization) effect is that it strongly happens-before any dynamic initialization. An implementation that simply performed all static initialization first at runtime and all dynamic initialization afterwards would be conforming. – user17732522 Aug 03 '22 at 00:17
  • Whether a `constexpr` function is evaluated as constant expression depends on the arguments. So if you pass something that isn't a constant expression you get runtime evaluation of the function. With `constinit` (and `consteval` I believe too) the function must be (able to) evaluated at compile time so such arguments should give errors: https://godbolt.org/z/1esYbGbT9 – Goswin von Brederlow Aug 03 '22 at 16:54

1 Answers1

10

The standard does not have the concept of "compile time". There is only "constant evaluation" and a "constant expression". Implementations may implement "constant expressions" such that they are evaluated at compile time.

But there is no C++ standard-defined behavior you can point to in the actual C++ program that makes a "constant expression" equivalent to compile-time compilation. It is 100% legal for a compiler to emit code for constant expressions, and it always has been.

From [expr.const]/13:

"An immediate invocation [ie: calling a consteval function] shall be a constant expression

So calling a consteval function is a constant expression. Does this mean that the compiler certainly will not emit actual assembly for this function that gets called at runtime? No; that's a matter of the quality of the implementation.

However, certain uses of a constant expression do inform various compiler decisions. This means that, while the compiler does not in general have to evaluate them at compile time, if the value is used in a place that affects the basic act of compilation, the compiler must in that instance evaluate the expression right then.

For example:

std::is_same_v<array<int, some_constexpr_func(3, 4)>, array<int, 7>>

Whether this (constant) expression evaluates to true or false depends on what some_constexpr_func(3, 4) returns. If it returns the sum of its parameters, it is true. And the compiler needs to know that, because it needs to emit the code for the std::array<int, S> type, which as part of its type includes its template parameters. So it needs to know what they are.

Outside of cases where constant evaluation affects compiler decisions, whether any constant evaluation happens at compile time is up to the quality of your compiler.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982