static const vs contexpr in c23: what's the point?
Pragmatically, I see little point to C23 constexpr in general, other than to make C slightly more source-compatible with C++ than previous versions were.
I understand that the appeal would be that constexpr objects evaluate to constant expressions,
Yes, and that makes constexpr objects a bit of a convenience feature, especially for people who look down their noses at macros. But there are also one or two other meaningful differences, which may have played a role in getting constexpr accepted.
whereas static const objects do not. My question is why don't they? To my understanding static const objects must be initialized with a constant expression anyway, why not just change the behavior of static const objects to evaluate to constant expressions?
In C, const is a type qualifier, whereas constexpr is a storage class specifier, notwithstanding that it implies const type. On the other hand, constexpr does not imply static storage duration. constexpr objects defined at file scope have static storage duration by virtue of that placement, but those declared at block scope have automatic duration unless also declared static. I am in no way suggesting that this is enough of a distinction to justify adding constexpr, but I do want you to recognize that constexpr to static const is, in general, an apples to oranges comparison.
Ultimately, it comes down to the committee's choices. I have no special insight into the committee's decisions, but these are some of the relevant priorities that I impute to it:
- to prefer convergence toward C++ over divergence from it
- to make C language features as orthogonal as possible
The committee having taken it in mind to add to C objects whose identifiers are considered constant expressions, both of those priorities favor doing it by adding constexpr over doing it by changing the semantics of const.
However, the main new thing that constexpr brings to C may be this:
NOTE 2 The constraints for constexpr objects are intended to enforce checks for portability at translation time.
(C23 6.7.2/18)
The main relevant constraint seems to be:
The value of any constant expressions or of any character in a string literal of the initializer shall be exactly representable in the corresponding target type; no change of value shall be applied.
(C23 6.7.2/5)
The spec gives several examples of definitions of constexpr objects that violate that constraint, where there would be no constraint violation if constexpr were changed to const. Basically, constexpr provides compile-time range and precision validation of the initial value with respect to the object's type.
There are a handful of other, minor, differences from const, but all of them are secondary to the choice to provide constexpr in some form at all.