<p>I noticed C23 has <code>constexpr</code> but only for variables, not for functions. While C++ does, this would allow a function to be defined in such a way that if the function is called with compile-time constant arguments, it can be evaluated at compile time, and thus the results are also compile-time constants. But C23 explicitly did not add this capability.</p>
<p>Why might it be impractical to evaluate a function at compile-time?</p>

<compilers><constant-expressions>
Why is it so hard to evaluate functions with constant arguments at compile-time?
CC BY-SA 4.0
2023-07-02T22:55:57.063Z
Hi there! I may be misinterpreting your post, but the question in the title seems to be a little different from the one in the question (why should a language have constant time evaluation vs why shouldn't a language have compile time evaluation of functions). Could you please edit your post to clarify which of those your question is?
user
CC BY-SA 4.0
2023-07-03T08:46:54.550Z
Not a C/C++ answer, but the [Jai](https://www.reddit.com/r/Jai/) programming language has a `#run` statement capable of running *any* code at compile time. [Here's](https://youtu.be/UTqZNujQOlA?t=4710) Jonathan Blow showcasing a Space Invaders game, where you have to play and win a round for the compilation to succeed.
BoppreH
CC BY-SA 4.0
2023-07-04T03:30:32.930Z
See also [this answer of mine](https://cs.stackexchange.com/a/144071/131656) and its question on CS SE. Some kinds of programs are difficult to write, and this influences interpreters and compilers. I'm asking a followup question which will hopefully bridge the gap between communities.
Corbin
<p>There are many things in C++ that C has chosen not to adopt. Every feature adds complexity to the language, which burdens both its users (since they may have to read or use code that uses it, even if they choose not to use it themselves) and compiler writers. C values simplicity much more than C++ does for a handful of reasons, some historical, some cultural, and some practical. C, much more so than C++, is often used as a “portable assembly” language, and C compilers exist for a staggering number of targets. Many of those compilers are quite simple, and they often struggle to implement even C99. While full <code>constexpr</code> support would not be a significant implementation obstacle for GCC or Clang, it would be for those smaller compilers.</p>
<p>Frankly, I suspect this appeal to simplicity is the main reason that C23 did not opt to adopt <code>constexpr</code> wholesale—after all, if people want to write C++, they can write C++—but perhaps it’s not entirely obvious <em>why</em> this feature adds so much complexity. For starters, let us agree it is desirable that expressions we evaluate at compile-time</p>
<ol>
<li><p>will not perform side-effects,</p>
</li>
<li><p>will produce a result in finite time,</p>
</li>
<li><p>behave in reasonably the same fashion on different platforms,</p>
</li>
<li><p>can be implemented in a reasonably efficient fashion.</p>
</li>
</ol>
<p>Point 1 is not terribly hard to guarantee with a sufficiently restrictive system. We will not be so lucky with the other ones.</p>
<p>In C++, <code>constexpr</code> already <em>fails</em> to provide point 2, as <a href="https://stackoverflow.com/questions/9201506/is-constexpr-based-computation-turing-complete"><code>constexpr</code> is Turing-complete</a>. This is not especially radical for C++, since C++ template metaprogramming was already known to be Turing-complete as well, but it would be quite a bit more significant for C. C could have chosen to implement a restricted form of <code>constexpr</code> functions that are not Turing-complete, but supporting functions certainly makes it harder to be completely confident some Turing-completeness hasn’t snuck in.</p>
<p>Furthermore, even if <code>constexpr</code> functions were restricted enough to ensure they were <em>not</em> Turing-complete, this may not be comforting enough to some people. After all, a Turing-incomplete language can still express programs that take arbitrarily long amounts of time to evaluate. C compilation is currently quite fast, while C++ compilation has a well-earned reputation for being glacially slow; I imagine the C standards committee was not keen on opening that can of worms.</p>
<p>Point 3 sounds easy enough in theory, but keep in mind that C (somewhat infamously) has a great deal of both undefined behavior and implementation-defined behavior. What should expressions that trigger undefined or implementation-defined behaviors do when evaluated at compile time? Are all compiler vendors required to detect it and signal an error? Or should it simply be undefined or implementation-defined? These are thorny questions, and I imagine the committee did not feel like they were worth hashing out.</p>
<p>Finally, point 4 requires that every C compiler have some mechanism of running C code at compile-time that probably <em>doesn’t</em> involve just running the whole pipeline, generating a native binary, linking it, invoking it, and extracting the result. Furthermore, this all needs to be done while the compiler is in the middle of compiling something else! This adds a great deal of complexity to the task of implementing a C compiler and likely involves <em>also</em> writing a C interpreter, which is not as easy a feat as it may sound, especially if you are doing it in a systems language.</p>
<p><strong>tl;dr</strong>: <code>constexpr</code> functions are just not in C’s complexity budget.</p>

CC BY-SA 4.0
2023-07-02T23:43:20.057Z
2023-07-02T23:43:20.057Z
Alexis King
CC BY-SA 4.0
2023-12-11T08:38:40.630Z
Point #2 could be tackled by specifying that the ability to resolve certain kinds of potentially-constant expressions is a quality-of-implementation issue, and allowing programmers to specify what should happen if a particular expression cannot be practically resolved at compile time (possible choices include lazy evaluation, initialization-time evaluation, falling back to normal function behavior, or refusing compilation).  The situations where compile-time resolution would be most useful largely coincide with those where it would be cheapest; cases where it would be more expensive...
supercat
CC BY-SA 4.0
2023-12-11T08:39:21.610Z
...would more often benefit from the use of an outside tool to perform the computations and embed the results into the source seen by a C compiler.
supercat
CC BY-SA 4.0
2023-07-03T02:40:30.350Z
C++ `constexpr` also behaves as a function color, which results in the vast majority of code being constexpr-by-default, even if in practice it's never called in a constant-evaluable way.
Bbrk24
<p>I have no specific insight into the decisions made by the C language designers, but I can offer a few reasons why this might not have been included:</p>
<ul>
<li><p>Replacing an expression like <code>foo(23)</code> with its result is only possible when <code>foo</code> is a <a href="https://en.wikipedia.org/wiki/Pure_function" rel="noreferrer">pure function</a>; if it has <a href="https://en.wikipedia.org/wiki/Side_effect_(computer_science)" rel="noreferrer">side-effects</a> then the compiled program must execute those side-effects, and if the function depends on external state or is otherwise not deterministic, then replacing it with a constant result would likewise change the program's behaviour. <a href="https://langdev.stackexchange.com/q/1031/89">Inferring or verifying purity isn't necessarily easy</a>.</p>
</li>
<li><p>Related to purity, if the function <code>foo</code> depends directly or indirectly on outside context (e.g. environment variables), then evaluating it at compile-time would use the environment from the machine it was compiled on, instead of the environment from the machine the program is run on.</p>
</li>
<li><p>If <code>foo(23)</code> does not terminate, <a href="https://en.wikipedia.org/wiki/Halting_problem" rel="noreferrer">the compiler would generally have no way of knowing this in advance</a>, so attempting to compute it at compile-time could hang the compiler in an infinite loop. Alternatively, the compiler might require extra complexity to stop trying to compute it at compile-time if it has taken too long. This could also cause the compiler itself to be non-deterministic, since the same program compiled twice might reduce <code>foo(23)</code> to a constant in one compilation but not the other.</p>
</li>
<li><p>If <code>foo(23)</code> takes a finite but very long time to compute, then the purpose of the program might be <em>to</em> compute it. In this case the user doesn't want it to be computed at compile-time, because the program might be intended to run on a different computer than the one it is compiled on. This is somewhat common in  <a href="https://en.wikipedia.org/wiki/Computational_mathematics" rel="noreferrer">computational mathematics</a>, where an exhaustive search for some combinatorial problem might be intended to run on a supercomputer, and be expected to take months to complete.</p>
</li>
</ul>
<p>These problems can be summarised as: the compiler can't easily infer which functions it is correct or possible to do this with, and the compiler needs to avoid doing it if the user <em>intends</em> for the function to be evaluated at runtime.</p>
<hr />
<p>Both of those concerns could be addressed by making compile-time evaluation of functions opt-in. Rust does this: <a href="https://doc.rust-lang.org/reference/const_eval.html#const-functions" rel="noreferrer">a function can be declared <code>const</code></a>, in which case the compiler checks that the function doesn't do anything not allowed in a <code>const</code> function, and the user can choose not to label functions as <code>const</code> if they are not intended to be evaluated at compile-time.</p>
<p>However, this adds to the complexity of implementing the compiler, since it's also necessary to check that <code>const</code> functions obey the rules. It's also necessary to specify what those rules are, so that different compilers will agree on which functions are allowed to be declared <code>const</code>.</p>

CC BY-SA 4.0
2023-07-02T23:37:25.790Z
2023-07-02T23:42:57.157Z
2023-07-02T23:42:57.157Z
kaya3
CC BY-SA 4.0
2023-07-02T23:45:35.653Z
We seem to have written almost identical answers. :) I think I’ll keep mine, since it mentions a couple other details, but +1.
Alexis King
CC BY-SA 4.0
2023-07-02T23:46:38.090Z
@AlexisKing There's some overlap but a lot in your answer that isn't in mine.
kaya3
CC BY-SA 4.0
2023-07-03T08:22:57.507Z
I've upvoted both - very interesting and separate points.
AnoE
# Why is it so hard to evaluate functions with constant arguments at compile-time?
Posted by: username_0 on 2023-07-02
I noticed C23 has `constexpr` but only for variables, not for functions. While C++ does, this would allow a function to be defined in such a way that if the function is called with compile-time constant arguments, it can be evaluated at compile time, and thus the results are also compile-time constants. But C23 explicitly did not add this capability.

Why might it be impractical to evaluate a function at compile-time?
**Tags:** compilers constant-expressions
> **Comments:**
> **username_1**: Hi there! I may be misinterpreting your post, but the question in the title seems to be a little different from the one in the question (why should a language have constant time evaluation vs why shouldn't a language have compile time evaluation of functions). Could you please edit your post to clarify which of those your question is?  
> **username_2**: Not a C/C++ answer, but the [Jai](https://www.reddit.com/r/Jai/) programming language has a `#run` statement capable of running *any* code at compile time. [Here's](https://youtu.be/UTqZNujQOlA?t=4710) Jonathan Blow showcasing a Space Invaders game, where you have to play and win a round for the compilation to succeed.  
> **username_3**: See also [this answer of mine](https://cs.stackexchange.com/a/144071/131656) and its question on CS SE. Some kinds of programs are difficult to write, and this influences interpreters and compilers. I'm asking a followup question which will hopefully bridge the gap between communities.  
---
## 2 answers
### Answer by username_4 on 2023-07-02. Score: 33
There are many things in C++ that C has chosen not to adopt. Every feature adds complexity to the language, which burdens both its users (since they may have to read or use code that uses it, even if they choose not to use it themselves) and compiler writers. C values simplicity much more than C++ does for a handful of reasons, some historical, some cultural, and some practical. C, much more so than C++, is often used as a “portable assembly” language, and C compilers exist for a staggering number of targets. Many of those compilers are quite simple, and they often struggle to implement even C99. While full `constexpr` support would not be a significant implementation obstacle for GCC or Clang, it would be for those smaller compilers.

Frankly, I suspect this appeal to simplicity is the main reason that C23 did not opt to adopt `constexpr` wholesale—after all, if people want to write C++, they can write C++—but perhaps it’s not entirely obvious *why* this feature adds so much complexity. For starters, let us agree it is desirable that expressions we evaluate at compile-time

1. will not perform side-effects,
2. will produce a result in finite time,
3. behave in reasonably the same fashion on different platforms,
4. can be implemented in a reasonably efficient fashion.

Point 1 is not terribly hard to guarantee with a sufficiently restrictive system. We will not be so lucky with the other ones.

In C++, `constexpr` already *fails* to provide point 2, as [`constexpr` is Turing-complete](https://stackoverflow.com/questions/9201506/is-constexpr-based-computation-turing-complete). This is not especially radical for C++, since C++ template metaprogramming was already known to be Turing-complete as well, but it would be quite a bit more significant for C. C could have chosen to implement a restricted form of `constexpr` functions that are not Turing-complete, but supporting functions certainly makes it harder to be completely confident some Turing-completeness hasn’t snuck in.

Furthermore, even if `constexpr` functions were restricted enough to ensure they were *not* Turing-complete, this may not be comforting enough to some people. After all, a Turing-incomplete language can still express programs that take arbitrarily long amounts of time to evaluate. C compilation is currently quite fast, while C++ compilation has a well-earned reputation for being glacially slow; I imagine the C standards committee was not keen on opening that can of worms.

Point 3 sounds easy enough in theory, but keep in mind that C (somewhat infamously) has a great deal of both undefined behavior and implementation-defined behavior. What should expressions that trigger undefined or implementation-defined behaviors do when evaluated at compile time? Are all compiler vendors required to detect it and signal an error? Or should it simply be undefined or implementation-defined? These are thorny questions, and I imagine the committee did not feel like they were worth hashing out.

Finally, point 4 requires that every C compiler have some mechanism of running C code at compile-time that probably *doesn’t* involve just running the whole pipeline, generating a native binary, linking it, invoking it, and extracting the result. Furthermore, this all needs to be done while the compiler is in the middle of compiling something else! This adds a great deal of complexity to the task of implementing a C compiler and likely involves *also* writing a C interpreter, which is not as easy a feat as it may sound, especially if you are doing it in a systems language.

**tl;dr**: `constexpr` functions are just not in C’s complexity budget.
> **Comments:**
> **username_6** (2023-07-03): C++ `constexpr` also behaves as a function color, which results in the vast majority of code being constexpr-by-default, even if in practice it's never called in a constant-evaluable way.  
> **username_5** (2023-12-11): Point #2 could be tackled by specifying that the ability to resolve certain kinds of potentially-constant expressions is a quality-of-implementation issue, and allowing programmers to specify what should happen if a particular expression cannot be practically resolved at compile time (possible choices include lazy evaluation, initialization-time evaluation, falling back to normal function behavior, or refusing compilation).  The situations where compile-time resolution would be most useful largely coincide with those where it would be cheapest; cases where it would be more expensive...  
> **username_5** (2023-12-11): ...would more often benefit from the use of an outside tool to perform the computations and embed the results into the source seen by a C compiler.  
---
### Answer by username_7 on 2023-07-02. Score: 18
I have no specific insight into the decisions made by the C language designers, but I can offer a few reasons why this might not have been included:

* Replacing an expression like `foo(23)` with its result is only possible when `foo` is a [pure function](https://en.wikipedia.org/wiki/Pure_function); if it has [side-effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) then the compiled program must execute those side-effects, and if the function depends on external state or is otherwise not deterministic, then replacing it with a constant result would likewise change the program's behaviour. [Inferring or verifying purity isn't necessarily easy](https://langdev.stackexchange.com/q/1031/89).
* Related to purity, if the function `foo` depends directly or indirectly on outside context (e.g. environment variables), then evaluating it at compile-time would use the environment from the machine it was compiled on, instead of the environment from the machine the program is run on.
* If `foo(23)` does not terminate, [the compiler would generally have no way of knowing this in advance](https://en.wikipedia.org/wiki/Halting_problem), so attempting to compute it at compile-time could hang the compiler in an infinite loop. Alternatively, the compiler might require extra complexity to stop trying to compute it at compile-time if it has taken too long. This could also cause the compiler itself to be non-deterministic, since the same program compiled twice might reduce `foo(23)` to a constant in one compilation but not the other.
* If `foo(23)` takes a finite but very long time to compute, then the purpose of the program might be *to* compute it. In this case the user doesn't want it to be computed at compile-time, because the program might be intended to run on a different computer than the one it is compiled on. This is somewhat common in [computational mathematics](https://en.wikipedia.org/wiki/Computational_mathematics), where an exhaustive search for some combinatorial problem might be intended to run on a supercomputer, and be expected to take months to complete.

These problems can be summarised as: the compiler can't easily infer which functions it is correct or possible to do this with, and the compiler needs to avoid doing it if the user *intends* for the function to be evaluated at runtime.

---

Both of those concerns could be addressed by making compile-time evaluation of functions opt-in. Rust does this: [a function can be declared `const`](https://doc.rust-lang.org/reference/const_eval.html#const-functions), in which case the compiler checks that the function doesn't do anything not allowed in a `const` function, and the user can choose not to label functions as `const` if they are not intended to be evaluated at compile-time.

However, this adds to the complexity of implementing the compiler, since it's also necessary to check that `const` functions obey the rules. It's also necessary to specify what those rules are, so that different compilers will agree on which functions are allowed to be declared `const`.
> **Comments:**
> **username_4** (2023-07-02): We seem to have written almost identical answers. :) I think I’ll keep mine, since it mentions a couple other details, but +1.  
> **username_7** (2023-07-02): @AlexisKing There's some overlap but a lot in your answer that isn't in mine.  
> **username_8** (2023-07-03): I've upvoted both - very interesting and separate points.  
---
