[RFC] -fimplicit-constexpr (original) (raw)
This is a proposal to add -fimplicit-constexpr (off by default) as an extension to Clang. This extension allows constant evaluation of functions not declared constexpr
(or consteval
) following limitations of the selected language mode.
This extension mirrors the same flag already shipped in GCC 12 and is highly useful for exploration work when making libraries constexpr-compatible (including the standard library for purposes of standardization). In my personal experience, it’s currently really easy and quick to explore code for constexprification in libstdc++ thanks to this flag in GCC. I would like to do the same thing with libc++.
Specification
Ignore expr.const#10.3
An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:
- …
- an invocation of a non-constexpr function
- …
Existing usage
This feature is used in some existing projects at github code search, but its main use-case in my opinion is a development tool for experimentation.
Relation to the C++ standard
It’s basically the same thing I proposed a long time ago in https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1235r0.pdf
The C++ specification for constant-evaluated code since then has changed significantly, focusing more on execution-related limitations (can’t evaluate for example reinterpret_cast) as opposed to the syntactic limitations of the past (can’t contain even unevaluated try/block). With this development, the constexpr
keyword in front of a function is basically an opt-in for constant evaluation, and this extension is an opt-in for “everything which can be constant-evaluated according to the language rules minus the requirement for the constexpr
keyword”.
In future, it is probably worth it to revisit the discussion of making the constexpr
keyword not needed for functions with a body.
Two approaches to implementation
There are basically two approaches to implementing this feature:
Making functions implicitly constexpr
We can automatically add constexpr
to all function declarations we see during parsing. This was the first approach I tried. But in older C++ versions, where the existence of some constructs (throw, unevaluated assembly) was directly forbidden in constexpr
functions, this led to problems in cases where a forward declaration was implicitly marked constexpr
, but the definition ended up containing such invalid constructs, which would cause us to reject the definition.
We can also check the function definition once we have body with CheckConstexprFunctionDefinition, and add constexpr
conditionally only if it’s compatible. But this will lead to the constant evaluator sometimes erroring out on a call to non-constexpr
function as a sole reason.
Removing checks in constant evaluator
As adding constexpr
to everything at the declaration site is user-hostile in older language modes and since doing it conditionally will lead to confusion, I have instead looked into removing the check for the constexpr
flag in the evaluator. This accomplishes the same result, and it also leads to better diagnostics, where the user will get the exact reason why something can’t be evaluated directly according to language rules and modes.
Problem with instantiation
The only problem I ran into was Clang implicitly instantiating member functions of templates only if they are marked constexpr
. This extension needs to implicitly instantiate all member functions regardless of them being constexpr
or not.
This is essentially the same behaviour as what would happen if these functions were marked constexpr
by the user at their declarations.
An alternative approach would be lazy instantiation only when needed, but that would require being able to perform template instantiation in the AST library, which is currently impossible and outside of scope of this extension. This is a long-standing issue, see also issue 59966,which we have already discussed at length in the context of C++26’s proposed define_aggregate
and friends.
To inline or not to inline?
The current GCC behaviour is to make functions implicitly constexpr
only if they are (implicitly or explicitly) declared inline
. I have talked with Jason Merrill who implemented this extension in GCC. He told me he is not opposed to allowing this for non-inline functions as well. The reason why he originally did it this way is that GCC by default (-fsemantic-interposition) avoids any interprocedural analysis in non-inline non-COMDAT functions in case they are replaced at runtime.
For now, the implementation requires free functions to be declared inline
(note that class members with an inline definition are of course implicitly inline
).
Implementation
I have the implementation available at PR#136436. It’s just a new flag and a function to query if a FunctionDecl
can be implicitly constexpr
. This query function is basically needed in three places: in SemaExpr.cpp
to make sure any member functions of templates are instantiated and in ExprConstant.cpp
’s CheckConstexprFunction
function, as well as in its equivalent in the new interpreter.
In order to be usable for experimentation when making libraries constexpr
-compatible, this needs to be implemented in Clang and can’t live outside the tree, as users would need to compile Clang themselves, which not every library developer is willing to do.
The PR contains tests checking various language versions and making sure that the presence of functions that are not constant-evaluatable in those language versions won’t break a build unless used explicitly in constant-evaluated context.