N2917 proposes constexpr functions and objects for C
Alex Gilding's revised proposal would let C functions participate in constant expressions while preserving ordinary runtime calls. Its restrictions expose the cost of adding compile-time evaluation to small C implementations.
C makes it awkward to give one calculation both a compile-time interface and an ordinary function interface. A macro can expand inside a static initializer, but it cannot supply a function address. A function gives the program a callable entity, but an ordinary function call cannot participate in the constant expressions that static initialization requires. Alex Gilding's N2917 proposal, dated January 25, addresses that division by proposing constexpr for functions as well as objects.
The distinction matters when reading this revision. This is a proposal for C23, with wording against draft N2731. It does not announce an implemented language facility or a completed committee decision. The WG14 document log identifies it as the second version of the constexpr specifier paper. Its technical scope includes function definitions, aggregate access, and the rules that determine which expressions qualify as constants.
C was my first language, and its small vocabulary remains part of its appeal. A change like this interests me because it removes a particular duplication in source code. It also asks compiler implementations to take on work that ordinary C expression evaluation has previously avoided.
Making a function usable during translation
N2917 deliberately starts with a restricted function body. Besides a single return statement, it permits null statements, typedef declarations, and static assertions. The return expression must satisfy the proposed constant-expression rules with the function's parameters treated as constant identifiers. A function must return a value. Mutable local variables and general statement execution are outside this design.
Under the proposal, a small calculation could have this shape. This is proposed syntax, not a claim that an existing C implementation accepts it.
static constexpr unsigned packet_bytes(unsigned payload)
{
return payload + 8u;
}
static unsigned char packet[packet_bytes(24u)];
The call uses constant arguments and follows a visible definition, so the proposed rules let its result provide an integer constant expression. At runtime, that function may receive a value known only at runtime, in which case it behaves as an ordinary function call. Its declaration therefore supplies one calculation for both uses without requiring a second macro implementation.
That does not make every invocation a translation-time operation. N2917 permits taking the address of a constexpr function, but says the function pointer does not preserve the constexpr specifier. Its wording for constant-expression calls requires the called expression to name the function directly. A program cannot infer compile-time callability merely from possessing a pointer to a function that happened to have a constexpr declaration.
Definition visibility is another deliberate restriction. The function must be defined in the translation unit before evaluation of the outermost constant expression that uses it. A declaration alone does not supply the expression evaluator with the calculation. That requirement also makes the proposal's interest in header definitions understandable.
Objects retain an identity
For objects, the proposed specifier fixes the value during translation and implies const qualification. An object still exists at runtime and has an address. That is a different abstraction from substituting tokens through the preprocessor. The proposal says even an automatic constexpr object has a unique address obtainable with the address operator.
The proposed wording distinguishes several kinds of constant expression. An integer constexpr object can provide an integer constant expression. An arithmetic object can supply an arithmetic constant expression. The paper also introduces wording for structure constant expressions and permits particular member accesses to contribute constant values.
Aggregate access has restrictions that deserve attention. Array subscripts must be integer constant expressions that designate elements within the array. Access to a union member qualifies only when it names the member that was initialized. These conditions keep constant evaluation tied to a known initialized value instead of allowing arbitrary inspection of runtime storage.
There is a useful inconsistency inside the paper itself. Its explanatory discussion says there are no restrictions on the type of constexpr objects, but the proposed normative wording prohibits pointer objects and aggregates containing pointer subobjects. The wording is the more precise guide to the actual change under discussion. A description of this revision should therefore avoid promising unrestricted pointer-bearing constant objects.
Why the restrictions exist
Gilding argues that the restricted function body keeps implementation costs near those of an expression evaluator. Substituting arguments into a return expression produces an expression tree that the compiler can evaluate using machinery it already needs for constants. Adding mutable locals and statement execution would require a more general interpreter within the compiler.
The paper reports implementation experience inside the QAC team with fitting C++11-style constexpr evaluation into a C evaluator. It also records feedback from the ChibiCC developer that implementation would not be hard. Those are specific claims about feasibility, not measurements of compilation time or evidence that every C compiler can add the facility at the same cost.
Recursion complicates the argument. N2917 revisits an earlier prohibition on recursive calls and discusses how an evaluator can expand only the branch that actually needs evaluation. Its proposed translation limits include 512 nested constexpr invocations and 1,048,576 nested constant expressions in one evaluation. These figures belong to the proposed wording. They are not runtime recursion guarantees or measurements of a shipping compiler.
My preference is to judge this addition by the abstractions it makes available without changing the runtime model. Static initializers and static assertions have concrete uses for ordinary typed calculations. The cost is a larger specification for which calls and aggregate accesses remain valid during translation.
Preserving existing C rules
N2917 explicitly avoids redefining const. In particular, an ordinary const object with static storage duration does not automatically become a constant expression under this proposal. The paper also preserves the existing distinction between C and C++ inline semantics. A constexpr function implicitly receives inline treatment, but the author does not propose importing C++ linkage rules wholesale.
That decision limits how much compatibility the shared keyword promises. The paper argues that constexpr can enlarge the set of headers useful to both languages. It does not claim to erase all differences in how those headers define objects or externally callable functions. Existing C programs must retain their meaning, including declarations that already depend on C's rules for const objects.
The proposal also explains why optimizer attributes do not solve the same problem. Information that helps the optimizer combine or remove calls does not necessarily make a function call legal inside a static assertion. Constant evaluation must affect the language's acceptance of the program before ordinary optimization. An attribute that may be ignored cannot safely determine whether that program is valid.
This revision leaves standard library annotations out of scope. N2917 proposes no Section 7 changes, and suggests considering individual library functions after the language facility exists. Even if its language wording advances, a programmer should distinguish a user-defined constexpr calculation from a library call whose declaration has received no corresponding change.