146 comments

[ 4.9 ms ] story [ 308 ms ] thread
What I often find myself lacking in C is a way to overload functions only on pointer constness. Consider

elem *array_get(array *a, int idx)

If I only have a const pointer to a, I cannot use this function. I have to add a second getter for const usecase, or use casting which seems unsafe.

I would like array_get to return a const pointer if the input was a const pointer, and a full pointer otherwise.

Macros can do this. But it would be nice if it worked for functions as well.

(comment deleted)
Well, you can slowly turn C in to C++... or you can just use a subset of C++.

As a C++ fan, and to play devils advocate, the argument against CV-qualifier overloading is that it often results in code duplication. I.e. the implementation of both array_get's would be syntactically identical. In modern C++ you can avoid this by making "array*" a template parameter (T*), and let the compile deduce the return type for the function (use auto) from what you actually return.

Many C++ haters don't get that a lot of C++ features build on others.

> Well, you can slowly turn C in to C++... or you can just use a subset of C++.

Those two are not the same.

It requires far more expertise and experience to be able to strong-arm C++ into generating lean C-like code than just writing C and getting what you want right out of the box.

> It requires far more expertise and experience to be able to strong-arm C++ into generating lean C-like code than just writing C and getting what you want right out of the box.

Even if that were true, and I protest that it isn't (I feel it's easier to generate good code in C++ than C), adding individual C++ features to C will just erode that ability to reason.

> I would like array_get to return a const pointer if the input was a const pointer, and a full pointer otherwise

Pointers (and other types) don't have "const-ness" attached to them - it's entirely dependent on the declaration of the function. Also, in C++ you can't overload on function return types.

My advice is to not bother using const much. It's quirky since data that is "const" for one part of the code might not be for another. But no one has figured out a way to represent such a constness-transition.

You can find this issue with APIs like "strstr()" for example. And IIRC Dennis Ritchie himself raised these concerns when const was conceived. The deeper issue I think is that const should be an attribute of the _code_ (as in, "I won't modify this data"), not of the data. Data is rarely truly constant (RAM cells are obviously writeable).

I use "const" mostly in "const char *" (for string-literals which are pointers to "reasonably const" memory, i.e. read-only mapped memory), and sometimes for function parameters to indicate that the function does not write through the pointer, but as in "strstr()" example this quickly gets quirky. Note that for this simple example you can work around if you take a const-pointer and return an index instead.

Summary: Be practical, don't overthink it.

> Data in general is never const

On MCUs const data (along with the whole .text section) is stored in flash which is very much const.

That's also why you want to use const whenever possible so you are not wasting precious RAM.

Already changed that to "is rarely truly constant". Also, that flash can be written, even if not during the lifetime of a process.

> That's also why you want to use const whenever possible so you are not wasting precious RAM.

My point is to not overthink placement of "const" in function parameters, local variables, etc. Because those don't make any difference to the compiled output. I already mentioned string literals as one type of "reasonably constant" data, and of course any other thing that goes in the .rodata section (such as globally declared const variables) is as much constant.

To wrap it up, the "const" keyword totally makes sense in global declarations to make them go to .rodata. Using const for type-checking purposes can get quirky on the other hand.

Most MCUs can mutate their flash, though, and most large embedded projects have one or two weird features that depend on mutating flash and then accessing it as const data. Also, Harvard architectures like AVR have different address spaces for flash vs. ram, so const variables still end up copied into RAM unless you qualify the variable types with non-standard decorators.

Being able to explicitly tell the compiler, "you can and should compile this out" seems like a good thing?

I use const frequently, because it helps prevent bugs. When something erroneously mutates immutable data, I want to know that as soon as possible, not months down the road when a corner case gets uncovered. One obvious example of this is thread-safety: you can't write to a const, so the compiler prevents you from introducing data races.

And yeah, sometimes it's annoying write const and non-const versions of the same code. But in my experience, those situations are quite rare and can often be factored out to one-liners; typically, only providing a const version is sufficient.

I mean, in C you can't overload functions on anything, including even the number of arguments... is this really the most interesting thing to want to overload? I'll claim the C way to do this is to just give yourself an array_get_const.
> C++ has supported compile-time evaluation of first-class functions for over ten years, while C is still limited to using second-class language features in compile-time contexts

It's funny that of all the gazillion features C++ has supported a for well over a decade the C folks have been missing out on, they pick exactly the one superfluous keyword in C++ to cherrypick onto C. You have to wonder what will happen if/when C++ removes the requirement for the keyword later.

Do you mean that C++ could have done without `constexpr` and could have used the existing `const`? Because that has been my feeling for a very long time. Why not expand the meaning of const in certain contexts. Compilers know if something can be evaluated at compile-time. No need for me to tell them explicitly.
It's not the same. e.g.

   const int x = readInput();
cannot be constexpr. Consider constexpr as a compile time assertion that a given symbol is available at compile time. If you didn't have the constexpr keyword then you would have have to inspect the definition to determine that every subexpression is constexpr.
"inspect the definition"

Yes, that's what I meant with "the compiler can do it".

Not given C and C++ separate file compilation.
That might be OK in an IDE driven language such as Java, but I doubt the neckbeards writing C in a plain text editor would go for that level of implicitness. In C++ it might be more acceptable since you practically need an IDE since C++11 to resolve all the auto type inference.
C neckbeard here. I do fully hope and expect and assume the compiler to fold constants and figure out as much at compile time as it can. This is the kind of optimization that doesn't make the code harder to reason about, even when coding with ed, since the logic and ultimate result of these expressions does not change.

Heavy type inference, overloading, and inheritance are a completely different beast and they make the code a lot more opaque.

(comment deleted)
Why not

  const int x = const foo(bar(y));

?
The compile can (and will) do that at run time, it just won't let the code alter x afterwards (either at compile time or run time)
Yes, all the constsomething is superfluous, as proven by Circle.

https://www.circle-lang.org/

Unfortunately, there is a big political war ongoing, so instead C++ keeps collecting const.... keywords.

I can't confirm in this specific case but, more often than not, the reason for these redundancies is a fraction of the committee felt uncomfortable with the implicit solution, so the keyword got added.
"Constexpr" is possibly the most hideous keyword in any programming language. My eyes vomit a little each time I read it.

They could have just used "pure" like in D, but this is C++, so that would be too sane and obvious.

Politics, check the outcome of the networking vote.
Introducing a new keyword means that all existing code that uses the new keyword as an identifier will break. That's why the `constexpr` keyword is deliberately weird.

It's also why they reuse keywords like `using`.

They could just use the reserved keyword naming like the C committee does.
No, not all const is superfluous. You don't want const-able method to be const. And const is a property of the declaration, not the definition. But constexpr is superfluous. It's something the compiler can infer purely from looking at the definition, and it has little to no "design" value like const does.
> You don't want const-able method to be const.

Edit, typo: I meant "You don't want every const-able method to be const."

Basically, yeah.

You don't even need 'const' for this. Under the as-if rule, subsets of the program could always already be evaluated at compile time and have the result substituted in the program. Compilers already did that when they had the definition handy. Like just do int x = f(5); and then define int f(int x) { return x * 2; } and you'll get int x = 10; in any compiler without any keywords whatsoever. The constexpr keyword just cluttered the code for no good reason.

In theory compilers can always optimize constant expressions, but they don't IRL. See my example above where I multiply the elements of an array containing {2,2}: https://news.ycombinator.com/item?id=28886999

My code is C style and unrealistically simple, yet the compiler can't even constant-fold that.

If you've looked at the output of real compilers, you'd know that it's unrealistic for a language to let you to use the result of an arbitrary function call where you'd normally use a constant.

I'd already replied to you on that example and explained this has nothing to do with optimizations as far as performance and mechanics go. The only relevance of optimizations is to show that the language semantics always permitted it. Not to suggest an optimize needs to be run to actually do this.
It depends.

For one thing, constexpr guarantees that errors will produce compilation failures. (At least it guarantees that when used in certain ways; unfortunately C++ overcomplicates things.) In a version of C that doesn't have an explicit constexpr keyword but does allow the compiler to treat arbitrary variables as constants, you could perhaps approximate this by using a value in a context that forces it to be a constant expression… e.g. `const int x = foo(); _Static_assert(x == x);`. But that's a hack. Much better to have an explicit way to say "please evaluate this at compile time".

Also, suppose the user doesn't use an expression in a way that forces it to be evaluated at compile time, but you still want to allow the compiler to do constant evaluation for the sake of faster runtime performance or smaller code size. In this example, should fib(35) be evaluated at compile time or runtime?

    unsigned int fib(int n) {
        return n <= 1 ? 1u : fib(n - 1) + fib(n - 2);
    }

    int main() {
        fib(26);
    }
It depends on the user's intent. In this case, evaluating fib(35) takes less than a millisecond at runtime on my computer, but evaluating it as constexpr with clang requires about a full second. (After all, naive fib performs an exponential number of recursive calls.) Sounds like it's probably a job for runtime… unless the user really values runtime performance or code size more than compile times (maybe they're targeting a really slow CPU), in which case they might prefer it to be done at compile time. Best to give them a choice.

Making things worse, the compiler has no real way of knowing how long something will take to constant-evaluate other than trying and seeing. It can give up after a timeout, but if the timeout isn't very tiny, it risks wasting a lot of time on aborted constant evaluation attempts. Therefore, while optimizers already try to perform opportunistic constant folding, they have to be very conservative with it. This problem doesn't occur if the compiler knows that something should be constant-evaluated because the user has marked it as such.

> In this example, should fib(35) be evaluated at compile time or runtime?

In that example, at compile time. If you write fib(35) you deserve to have to wait for the compiler to do it at compile time instead of at run time. (I'm not saying this to be funny. I'm really trying to make a point. You really do need a realistic example to motivate your side of the argument. If they're hard to come up with, that's kind of my point.) And honestly if I wrote that I would fully expect my compiler's optimizer to already waste that time constant-folding anyway; if I was concerned about its compile time I wouldn't write that even without constexpr.

> Best to give them a choice.

A need for that choice (assuming it's even necessary here, which is debatable, see above) doesn't mean you need a new keyword, especially not for functions. It could've been an attribute, and whatever it is, it only really makes sense on variables, not functions. And whatever it is, turning it into an exclusion instead of an inclusion would probably make sense here, seeing as how so much of the standard library has constexpr in front of it.

What? Why would fib(35) be evaluated at comple time? What if it's fib(1500)? The computers I want to run the program on is way faster than the pi I use for development.
> What? Why would fib(35) be evaluated at compile time?

Same reason why fib(2) might be.

> What if it's fib(1500)?

Are you familiar with exponential growth? This would take longer than the heat death of the universe, whether you evaluate it at compile time or run time...

Right, but maybe a military supercomputer could compute such a thing. I know my pi surely can't.
If nothing else, constexpr as a keyword lets you force it to happen and fail if it can't happen, rather than silently reverting back to runtime execution. I don't think it's certain that C++ will remove the keyword.
constexpr on a variable declaration does that (i.e. it's still superfluous on function declarations, which is where it clutters code by far the most). But even for that, there was already std::integral_constant which would've forced this, and they could've just updated it to C++20 via template<auto Value> static const auto constant = Value; if they really wanted to. Or added an [[attribute]] for variable declarations, just like how we have [[likely]] etc. No need to introduce a whole new keyword for this, especially not in a ton of other contexts.
Those suggestions, honestly, don't sound better than the keyword to me. I think you're taking it as a given that if a keyword can be avoided, it must be avoided, but I don't fundamentally agree with that at all.
I mean your (only?) argument for the constexpr keyword at this point is "it can be a keyword and I feel like it should be, even when I acknowledge it's redundant" then by all means go ahead. Personally I don't think that's a good way to do language design, but I can't argue with subjective feelings and aesthetics and I was never intending to. I'm pretty sure that's not one of the bases on which it made it into the standard though, and those are what I'm trying to address.
Is it superfluous? I will admit that when I first read about constexpr my first thought was 'why can't the compiler figure this out automatically?'. I assume that compiler vendors all agreed that a new keyword was necessary, they don't add them without reason.
It can, check the C++ compiler Circle.

https://www.circle-lang.org

Unfortunately it isn't a direction WG21 is willing to adopt.

I just assumed the constexpr keyword was added to help the programmer ensure that what they were writing could be run at compile time.
> I will admit that when I first read about constexpr my first thought was 'why can't the compiler figure this out automatically?'.

I would argue that the main value proposition of constexpr is not what the compiler can figure out automatically but allowing the developer to express where he wants constexpr and afterwards allow the compiler to verify if that's possible.

It's the difference between a soft "nice-to-have" requirements and a hard "must-have" requirement.

> I would argue that the main value proposition of constexpr is not what the compiler can figure out automatically but allowing the developer to express where he wants constexpr and afterwards allow the compiler to verify if that's possible.

If you want to look at it that way, it only applies to variable declarations at best. Not function declarations, which is the most common case of where it's used (and it completely clutters the code).

And honestly, if that was the value proposition, they could've just done it a million other ways. Like think even 'template<auto V> static const auto constant = V' would let you do constant<whatever> and verify it at compile time. Yeah it's C++20 but they could've still done it then or used a different approach, there are lots of ways to address this (attributes, etc.). No need to clutter every function prototype in the language for something that small.

Also I think you're thinking more of consteval than constexpr.

> If you want to look at it that way, it only applies to variable declarations at best. Not function declarations, which is the most common case of where it's used (and it completely clutters the code).

Not really. The whole point of constexpr is to state that an expression is constant, thus the value returned by the function is computed at compile time.

> And honestly, if that was the value proposition, they could've just done it a million other ways.

To me it seems thay annotating a function with constexpr is the clearest and most straight-forward way to go about expressing constant expressions.

> Also I think you're thinking more of consteval than constexpr.

To each his own, but if the whole point is to represent constant expressions then the constexpr keyword seems to be straight-forward.

> Not really. The whole point of constexpr is to state that an expression is constant, thus the value returned by the function is computed at compile time.

That's not even one point of constexpr, let alone the whole point. Putting constexpr in front of a function declaration does absolutely nothing to the codegen. See for yourself: https://gcc.godbolt.org/z/34h6Tanzn

Notice that both h() and f() are called despite being constexpr.

> To me it seems thay annotating a function with constexpr is the clearest and most straight-forward way to go about expressing constant expressions.

Again, it doesn't do what you think (see above). It does absolutely nothing to a function.

> To each his own, but if the whole point is to represent constant expressions then the constexpr keyword seems to be straight-forward.

This isn't an opinion thing. The consteval literally does what you're imagining constexpr does. Just replace constexpr with consteval in the code above. (I could probably argue that one has little use too, but that's not what I'm arguing here.)

(comment deleted)
You're seeing the compiler opportunistically make an optimization by taking constexpr as an extra inlining hint. I showed you it's not something you can rely on in my example. You have to realize constexpr only talks about what is allowed to happen, not what is guaranteed to happen. You want consteval for that.
Of course I want consteval, if a later C++ is available to me, but that doesn't excuse you writing constexpr does absolutely nothing, when it clearly does. It's no surprise your example doesn't fold calls without `-O3` or that one could produce a `constexpr` that doesn't fold to a constant (go ahead and try though :>).

But consteval function is also not a literal replacement constexpr. For example, you can't take a pointer to the former. There's a reason they co-exist and are not useless.

> Of course I want consteval, if a later C++ is available to me, but that doesn't excuse you writing constexpr does absolutely nothing, when it clearly does.

consteval wasn't intended to be excusing anything. I was just letting you know you're misunderstanding what constexpr does, and that what you're thinking it's supposed to do is in fact consteval's job.

You seem very confused about language specifications. That GCC and Clang happen to interpret constexpr as an extra "please inline" hint under -O3 is not evidence of constexpr "doing" something, and certainly not justification for including it in the language standard. That's like saying HN's orange color is "doing something" just because you really like to go eat an orange every time you see it.

I suspect you will have a hard time finding any evidence that constexpr was ever intended to be a "this is technically optional but please really, really try to inline this" hint of sorts to the compiler by the committee.

> But consteval function is also not a literal replacement constexpr.

I didn't say it was.

> For example, you can't take a pointer to the former.

"Therefore constexpr is useful" does not follow from this.

> There's a reason they co-exist and are not useless.

There's a mediocre reason for consteval, and a bad reason for constexpr. The former is probably unnecessary IMO though still of marginal utility, and in any case I can concede there's a debate that could be had there. The latter would be practically useless if the language cared to just make it the implicit default. It's really only solving a problem it created.

If you're using constexpr as an "optional but highly recommended inline" hint like you're showing me in your examples, you're doing the equivalent of pulling up HN's orange color so it coaxes you (or someone else) to go eat an orange. You are certainly welcome do that (hopefully it's a free country where you live) and I'm not stopping you per se, but it's missing both the letter and the spirit of the keyword(/color) by a colossal margin.

The compiler can figure it out. But that doesn't solve the problem constexpr was created for. Without constexpr compile time evaluation becomes very brittle. Constexpr is there to help the compiler and developer reach an understanding of the stuff that has to be evaluated at compile time.
The problem is that (especially library) code just becomes absolutely littered with constexpr, because realistically speaking, you do want everything that can be constexpr to actually be constexpr.
Why is that a problem? I actually really like seeing if a library function can be cte'd.
> Why is that a problem?

Because whether a call should be CTE'd is a decision for the caller to make, not the callee.

> I actually really like seeing if a library function can be cte'd.

That's not what you're seeing with constexpr. It's what you're seeing with consteval. [1]

What you're seeing with constexpr is which functions are prohibited from being CTE'd. Which is pretty useless when you don't intend to CTE them in the first place, and actively counterproductive when you do.

[1] Actually this is kind of a lie too [2], but at least it's much closer to the truth.

[2] Notice that seeing consteval here tells you absolutely nothing: https://gcc.godbolt.org/z/bWzd4v6Y1

consteval says it MUST be constant evaluated not that it CAN be constant evaluated. That is very different. With consteval it's not up to the caller to make the decision of whether to run at compile-time or not, the caller must provide constexpr arguments. With just a constexpr annotation the caller can decide to run it either at compile-time or run-time.

See this code:

https://godbolt.org/z/oMWKcWW5e

Like I said:

>> [1] Actually this is kind of a lie too [2], but at least it's much closer to the truth.

>> [2] Notice that seeing consteval here tells you absolutely nothing: https://gcc.godbolt.org/z/bWzd4v6Y1

Note I didn't claim this was their only difference.

I don't see what's your problem with libraries marking their functions as constexpr then. The library says it's up to the caller to decide whether this function is constant evaluated. Precisely what you wanted. If libraries start marking their function as consteval. Now that would be a problem.
> I don't see what's your problem with libraries marking their functions as constexpr then. The library says it's up to the caller to decide whether this function is constant evaluated.

The problem was explained here: https://news.ycombinator.com/item?id=28895123

The language could just say everything that could be constexpr is implicitly constexpr, and you'd get just as useful of an effect (even on existing code!) without anyone having to do anything extra. It's very rare for a function that could be constexpr to be justifiably prohibited from being used as such, and they could've introduced an opt-out attribute for that if they really cared.

You are going in circles...

constexpr is an interface guarantee. If it were not there the entire optimization stack would become brittle. Imagine updating a dependency or rewriting a function and suddenly your program is 10x slower. Things like this are known in other languages. For example Haskell and fusion optimization. It's fine for you to disagree with this reasoning. But then you should not be using C++ as you don't really care for performance and can use many other better suited languages.

Then you say constexpr doesn't make it possible for the caller to specify whether something is cte'd while this is precisely what the caller decides. You are confusing constexpr and consteval. consteval doesn't make it possible for the caller to decide whether the function is cte'd.

Please answer the points I have brought up instead of simply referring to something I have already refuted.

> Imagine updating a dependency or rewriting a function and suddenly your program is 10x slower.

That can already happen and will still be possible after constexpr. And it was always preventable by forcing evaluation of the variable in a CTE context, like in std::integral_constant. And you're missing the fact that an implicit constexpr with an explicit opt-out needn't behave any differently from one with explicit opt-in. Same semantics, just fewer keystrokes.

> constexpr is an interface guarantee

> Please answer the points I have brought up instead of simply referring to something I have already refuted.

You didn't actually refute it (though you tried), and I already did address them:

>> [2] Notice that seeing consteval here tells you absolutely nothing: https://gcc.godbolt.org/z/bWzd4v6Y1

Replace consteval with constexpr and you'll see it told you absolutely nothing about bar()'s CTE'ability. Observe how the callee used constexpr and bar() failed to CTE despite being constexpr: https://gcc.godbolt.org/z/odqbT7x9j

> You are going in circles...

Because you went in circles. I've directly addressed every single one of your points but then you looped back to ask the same question as you did in the beginning. Same questions, same answers.

> [2] Notice that seeing consteval here tells you absolutely nothing: https://gcc.godbolt.org/z/bWzd4v6Y1

In that example marking bar as consteval does nothing. Because you are not ever calling bar... Calling bar shows that it's not possible because foo is not constexpr:

  note: non-constexpr function 'foo<int>' cannot be used in a constant expression
  consteval T bar(T x) { return foo(x); }
Marking foo as constexpr makes it compile and compile time evaluate both foo and bar.

> Replace consteval with constexpr and you'll see it told you absolutely nothing about bar()'s CTE'ability. Observe how the callee used constexpr and bar() failed to CTE despite being constexpr: https://gcc.godbolt.org/z/odqbT7x9j

It doesn't compile because foo is not constexpr. Marking foo as constexpr makes it compile and foo and bar are evaluated at compile time. It produces this ASM:

  main:                                   # @main
          pushq   %rbp
          movq    %rsp, %rbp
          movl    $0, -4(%rbp)
          movl    $0, -8(%rbp)
          xorl    %eax, %eax
          popq    %rbp
          retq
At least try to the examples you are using to make your point. Because they in fact show that you are wrong...
The examples are not wrong, you're just not understanding them.

> It doesn't compile because foo is not constexpr.

Well obviously. That's not the question here. The question is, explain to me exactly what guarantee the declaring bar() as 'constexpr' achieved for anyone? It clearly didn't prevent its author from calling a non constexpr function, and it clearly didn't signal to its users that it is usable from a constexpr context either. The user had to go inspect each one of its callees manually to see if they could all be evaluated at compile time in reality, which is exactly what you just did with foo(). Which is pretty damn hard (as in, Turing-undecidably-hard) when things are templates and you have ADL on top of that. And which the presence of constexpr on bar() didn't help you one bit with.

The declaration of bar() told you exactly nothing by having constexpr in it. It made no difference whatsoever for itself, nor for main(). It just added clutter.

The code doesn't compile, so it did in fact stop the author from calling a non-constexpr function from a constexpr function. You can't meaningfully semantically talk about code that doesn't compile.
> You can't meaningfully semantically talk about code that doesn't compile.

You can point out that it failed to compile, and that can be a benefit when compiling would've been detrimental. Just like you yourself literally just did when you said "The code doesn't compile [...]".

> it did in fact stop the author from calling a non-constexpr function from a constexpr function.

The constexpr in front of bar() is not what prevented compilation. You don't believe me? Just take it out and see. The code would've failed to compile the exact same way without constexpr in front of bar(). Try it before disagreeing with me.

You're saying putting constexpr in front of bar() has a benefit. I'm saying: tell me what it was. Making that constexpr be always in front of bar() implicitly would've resulted in exactly the same outcome in every situation where the code compiles and also where it fails to compile (like above). Nothing would've been any different. I don't get why this is so difficult but I'm not going to keep repeating myself. Everything I can possibly imagine explaining is in my comments up to this point so just re-read them, I have no other way I can phrase these. Everyone else has understood what I'm saying and I'm at a loss how else to explain it differently. I promise you it's not wrong, we're just failing to communicate.

> It's funny that of all the gazillion features C++ has supported a for well over a decade the C folks have been missing out on, they pick exactly the one superfluous keyword in C++ to cherrypick onto C.

It looks to me that it's a significant improvement that's also a very low hanging fruit, given that C/C++ compilers that already support C++'s constexpr can trivially support it for C as well.

Not every C compiler is a C++ compiler.
Besides that, I suspect nobody in this thread has looked at what compilers output.

Compare the following programs that multiply 2*2:

https://godbolt.org/z/avsac77aa

https://godbolt.org/z/Mznhrf1sq

The only difference between them is that `len` and `result` are declared constexpr in the first program.

Notice how without constexpr, both clang and gcc generate a slow call to mul() inside main() instead of just calculating 2*2 at compile time.

I'm sure you could use LLVM opt to force to compiler to try harder than -O3 (and maybe that should be the default), but that will also make compile time longer. Real code is way harder to optimize than my 18 lines that are all in 1 file.

(comment deleted)
> I'm sure you could use LLVM opt to force to compiler to try harder than -O3 (and maybe that should be the default), but that will also make compile time longer. Real code is way harder to optimize than my 18 lines that are all in 1 file.

Evaluating code at compile time is not harder or even as much work as than optimizing it. If you ever wrote an interpreter you know that it's much simpler than optimized codegen. You can evaluate unoptimized code at compile time perfectly fine, and deducing whether that is possible is extremely fast in comparison. Which is why in C++ you see people litter constexpr everywhere they can regardless of whether the code is -O0 or -O3.

Hey, by all means fix clang and gcc so that my example no longer requires a runtime call to mul().

After all, evaluating code at compile time is easy right?

If you succeed in solving this problem in general, feel free to propose a change to the language that allows the use of expressions such as `mul(arr, arr+len)` anywhere you'd normally be required to use a constant. (That's what constexpr already achieves today)

Btw I mentioned that you can make the compiler try harder with LLVM opt because constant-folding is an optimization. I dunno why you bring up codegen.

Since the compiler must verify the expression is constexpr-valid, it could annotate that automatically instead. However, I believe that generally the standard requires that the keyword be explicit so that compilers agree on what are legal programs, and don’t just declare it to be implementation-defined for what is legal in const-initializer contexts.

But in clang, you’d need an frontend-optimization framework to do that reliably (due to recursion), and it doesn’t have one. In llvm, the Attributer framework is still relatively new, but is progressing towards that.

> However, I believe that generally the standard requires that the keyword be explicit so that compilers agree on what are legal programs, and don’t just declare it to be implementation-defined for what is legal in const-initializer contexts.

The standard could just declare that anything that could be constexpr-evaluable must be evaluated at compile time up to a depth of at least N, or something like that, after which it is permitted (but not obligated) to be evaluated at runtime. Just like with templates, except over there the implementation just errors out if the template gets too complicated or impossible to evaluate. The standard already has minimum criteria like these for a ton of other cases so this wouldn't be any different to justify a keyword for it.

> Hey, by all means fix clang and gcc so that my example no longer requires a runtime call to mul().

They can just pretend every inline function has constexpr in front of it, and fall back to compiling them if they fail to evaluate it as constexpr. The constexpr doesn't tell the compiler anything it didn't already know or have to verify anyway. And bear in mind much of the standard library already has constexpr in front of it (and that's a pretty strong signal that it should've been implicit IMO). I don't know Clang or GCC's codebases to know exactly what to modify off the top of my head, but I'm pretty sure adding an extra 'constexpr' internally in the front-end would be utterly trivial as far as compilers go. No need for any optimizations to get involved (and it's important to understand this).

> You have to wonder what will happen if/when C++ removes the requirement for the keyword later.

I don't think it's that simple. You need a way to differentiate code that runs at compile time and code that runs at runtime (because at compile time you can afford a "slow" algorithm, while at run-time you'll be calling into a proprietary binary which will do the computation on a GPU or something like that).

> I don't think it's that simple. You need a way to differentiate code that runs at compile time and code that runs at runtime (because at compile time you can afford a "slow" algorithm, while at run-time you'll be calling into a proprietary binary which will do the computation on a GPU or something like that).

You mean std::is_constant_evaluated()? You don't need constexpr to be able to have that.

but how will you differentiate a call that you want to perform at compile-time vs at run-time? e.g. if you want to run some unit tests to check the two algos, today you'd do:

    int res = my_algo();
    CHECK(res == 123);
    ...
    constexpr int res = my_algo(); 
    CHECK(res == 123); // compile-time version is the same
with

    int my_algo() { 
      if constexpr(is_constant_evaluated()) { 
        return 123;
      else
        return some_lib->very_advanced_computation();
    }
how do you do the same without the constexpr keyword ? i'd rather not introduce a template struct just to get a constexpr context, à la

    template<auto Value>
    struct manually_constexpr { static constexpr auto value = Value; };

    CHECK(manually_constexpr<my_algo()>::value == 123);
"constexpr" does not do what you think it does.

For example, "if constexpr(is_constant_evaluated())" is always true, I think.

See also the new C++ keyword "consteval" (yes, really!)

> but how will you differentiate a call that you want to perform at compile-time vs at run-time?

You could never do that to begin with. The compiler was always free to evaluate your function at compile time (and already did so in some cases).

> e.g. if you want to run some unit tests to check the two algos

Just make two separate functions and compare their outputs. That's what functions are for! So you don't put everything in one giant block of code.

> i'd rather not introduce a template struct just to get a constexpr context, à la

The standard library could do that for you instead of introducing a new keyword. In fact it practically already did so with std::integral_constant; they just need to update it for C++20 to avoid the redundancy.

Or they could've introduced a [[pure]] attribute, or other less intrusive mechanisms. The keyword wasn't necessary for anything you mentioned.

Also, as the sibling comment said, you might want to check out consteval.

> You could never do that to begin with. The compiler was always free to evaluate your function at compile time (and already did so in some cases).

no, that is false. the constant expression context is specified by the standard - the following will always hold if your compiler is not braindead : https://gcc.godbolt.org/z/zEEbWbhvr

>> You could never do that to begin with. The compiler was always free to evaluate your function at compile time (and already did so in some cases).

> no, that is false.

Your sentence is false, not mine. See below.

> the constant expression context is specified by the standard

Expressions can be evaluated at compile-time outside of "constant expression contexts", under the as-if rule. That's one of the main things compilers do when optimizing code.

> Expressions can be evaluated at compile-time outside of "constant expression contexts", under the as-if rule.

yes, but that does not make it a constexpr context, which is something explicitely specified by the standard. See my code example: the compiler is obviously able to evaluate foo() at compile time, yet it knows that the evaluation it does at compile time will yield a different result that the one it does in a constexpr context.

> yes, but that does not make it a constexpr context

It seems like we're talking past each other here. You wrote

"How will you differentiate a call that you want to perform at compile-time vs at run-time [without constexpr]?"

And I've been replying to that by pointing out that your run-time code can still be evaluated at compile time even if you don't use constexpr. Heck, I'll point out now that it can still be evaluated at run-time even if you do use constexpr! [1] It's neither here nor there.

Now you're talking about whether "constexpr contexts" do this, which is a separate question. Incidentally, even constexpr contexts don't distinguish between compile-time and run-time evaluation in anywhere except the condition itself [1], which is not the part you generally care the most about. If you want compile-time evaluation, you need to force its evaluation at a location where it's required to be evaluated at compile time, which is either a template parameter or a constexpr variable declaration.

You seem to be desiring consteval when you talk about constexpr.

(And you still haven't addressed the fact that your unit-test example is better served by having two separate functions!)

> the compiler is obviously able to evaluate foo() at compile time, yet it knows that the evaluation it does at compile time will yield a different result that the one it does in a constexpr context.

Citing this ability as if it's desirable is really strange. If your code does that it's buggy. A good language would outlaw this, not embrace it.

[1] https://gcc.godbolt.org/z/Yhqvqz5Pc

> "How will you differentiate a call that you want to perform at compile-time vs at run-time [without constexpr]?"

right, I should have wrote "in a constexpr context", e.g. what you find in template<invocations>, etc.

> And I've been replying to that by pointing out that your run-time code can still be evaluated at compile time even if you don't use constexpr.

I don't understand what this does have to do with the rest. That's just an optimization (which is not "consistently observable" and depends on the whims of the compiler). Making a variable constepxr is something that causes consistently observable results, which is much more useful. Incidentally, I have had multiple cases where things that could obviously be computed at compile-time weren't optimized in such a way by clang or gcc unless I did

    constexpr auto foo() { constexpr auto bar = some_complicated_function(...); return bar; }
instead of

    constexpr auto foo() { return some_complicated_function(...); }
with constexpr, if the compiler cannot compute the thing at compile time I get an error (which is good !)

> You seem to be desiring consteval when you talk about constexpr.

what I actually desire deep down is a way to have aribtrary parametrisable contexts, something like a generalized version of http://www.jot.fm/issues/issue_2008_03/article4/ ; having two is already a good start.

> (And you still haven't addressed the fact that your unit-test example is better served by having two separate functions!)

well, because I disagree. If the name my function is e.g. strlen I definitely don't want a strlen_constexpr and strlen_nonconstexpr pair of functions, I want the good choice to be made depending on the context I'm in ; I want to write "strlen" in generic code and have the correct thing happen no matter what.

to give you my general perspective, constexpr could call itself super_bamboozle that I couldn't care less, I just want "multiple worlds" being available in my programming language. what name they take is of very little importance.

> what I actually desire is a way to have aribtrary parametrisable contexts, something like a generalized version of http://www.jot.fm/issues/issue_2008_03/article4/ ; having two is already a good start.

I actually wholeheartedly agree with that. I've wanted something pretty similar for a long time. I just also wholeheartedly disagree on constexpr having been a good start on that.

> Incidentally, I have had multiple cases where things that could obviously be computed at compile-time weren't optimized in such a way by clang or gcc unless I did [...]

And this is one place we circle back to my argument, which is that implicit constexpr should be the default wherever possible. That way you don't e.g. run into situations where you forgot the keyword by accident, because you won't need it to begin with.

> well, because I disagree. If the name my function is e.g. strlen I definitely don't want a strlen_constexpr and strlen_nonconstexpr pair of functions, I want the good choice to be made depending on the context I'm in ; I want to write "strlen" in generic code and have the correct thing happen no matter what.

I think you misunderstood the proposal then. I was saying those functions would be only called directly for tests. Everywhere else, you'd use your strlen() with std::is_constant_evaluated() (or whatever) dispatching to one of those two functions, which would be implementation details hidden away from the user.

I think you're completely ignoring more radical proposed changes like lambdas: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2738.pdf and more modern error handling: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2361.pdf

Also consider that proposals must satisfy http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2021.htm small list of relevant points:

  4. Avoid “quiet changes.” 

  6. Keep the spirit of C.

  9. Minimize incompatibilities with C90 (ISO/IEC 9899:1990).

  11. Maintain conceptual simplicity.
I'm not ignoring anything, I just mentioned C++ has had a ton of features over C, some well over a decade (and some just about and some newer).

Moreover I can't even see how I could've mentioned lambdas. They have a superfluous keyword? Which one?

Edit: Whoops, just had a chance to read these comments more carefully. I didn't realize you're talking about C rather than C++. I didn't ignore them; I just wasn't aware of those proposals. (Certainly I haven't seen them on HN, and I don't keep up with all the proposals.)
Not sure if you were trying to make this point, but as others have noted constexpr almost certainly violates #11 (by mixing run-time and compile-time variables) and through that it also violates #6.
The two things I would add to C are RAII (like GCC __attribute__((cleanup)) and glib's g_autoptr wrappers), and variant types (mostly so that we could have an equivalent of Rust's Result type).
RAII is mostly already there, or at least a C-spirited version of it, thanks to variable length arrays. Unfortunately, there's little compiler support for large vla, both clang and gcc seem to choke on them because they put them on the stack (which is the naive implementation of vla).
How would variable length arrays, for example, help with releasing a mutex on early returns from a function?
They wouldn't, of course! Releasing resources silently (other than the memory of automatic variables) is very un-C-like.
> RAII is mostly already there

>> Releasing resources silently (other than the memory of automatic variables) is very un-C-like

The "R" is literally "resource", right? Sounds like instead of RAII you just meant unique_ptr.

Though I beg to differ on whether releasing memory automatically is any different from releasing other resources as far as being C-like is concerned!

VLA are out of the races since C11.

So you can forget about compilers caring to improve the quality of an optional feature, that was even cleaned from Linux kernel.

I'll give you my VLA and my complex.h when you pry it from my cold, dead hands.
You are always free to keep your own fork of GCC.
... constexpr isn't superfluous.

The compiler can/could/does absolutely infer that a thing is a compile time constant and do it for you.

But, if you declare a thing constexpr and someone updates some part of it to be !constexpr, you get a nice compiler error. Without labeling the value as constexpr, you don't get that certainty.

Edit: is the argument that const static is also that? (If so, only for direct initialization of structures / you still can't have the equivalent of a constexpr function).

> But, if you declare a thing constexpr and someone updates some part of it to be !constexpr, you get a nice compiler error. Without labeling the value as constexpr, you don't get that certainty.

That's not really true. You don't get that "certainty" with constexpr either:

  template<class T> T f(T);
  template<class T> constexpr T g(T x) { return f(x); }
Notice you won't even get a warning with -Weverything under Clang!

> Edit: is the argument that const static is also that? (If so, only for direct initialization of structures / you still can't have the equivalent of a constexpr function).

For variables, const static should probably evaluate at compile time whenever possible, like you mentioned.

For functions, constexpr is really useless (again, see above as just one example) and an inline declaration serves pretty much the same practical purpose. I think you're thinking of 'consteval', which is at least not completely useless, so it's a little easier to justify. Even then, it should've probably been an attribute rather than a keyword.

Hmm, is that just because of the template resolution? (and so wouldn't apply to constexpr in C)

AFAICT, something about the template declarations is making the call of f(x) to be "who could know" (which should just be fail).

The same thing with concrete types says that f isn't constexpr [3]. And you have to make it actually constexpr by removing the static from counter [4].

[1] https://docs.microsoft.com/en-us/cpp/cpp/constexpr-cpp?view=...

[2] https://godbolt.org/z/7rGWbP1xz

[3] https://godbolt.org/z/5WjMr3Kjv

[4] https://godbolt.org/z/v9rc1j9zc

I think it's because of ADL, which also wouldn't apply in C.
Edit: Actually maybe I'm wrong... even if you scope it it won't warn you.
They should add `pure` function attribute instead, also maybe the ownership.
> * They should add `pure` function attribute instead, also maybe the ownership.*

The C standard committee only adds suggestions that have been presented. If you feel strongly about those features, feel free to put together a proposal like the one that sparked this discussion.

I have experience with proposing new features to languages and that's taxing. You have to deal with constant bikeshedding, provide rationales, states of art analysis for different languages etc. And your proposal can be still stalled for political reasons - eg. there is another proposal and it's expected that certain behaviour will be consistent between both.
I think there’s a very high bar to clear to choose any other keyword than “constexpr”. With “constexpr” code can be compiled without change (at least in this regard) with C or C++ compilers, the keyword is the same. Good C and C++ interop is extremely important.

If you choose any other keyword we will end up with nasty macro ifdefs to detect C or C++ and choose the appropriate keyword. And worse, the burden will be on the programmer to do this.

This is at most tangentially related, but I could never figure out how to submit proposals for the C standard. How does this work?
In the olden days, the idea was that you did not start by submitting any proposal. First you got your pet language feature implemented (as a non-standard extension) in several major compilers and used in several meaningful code bases. And then you submitted that feature, which was already well understood and under widespread use, to the C standard committee, which would very likely accept it. Nowadays it seems that any old doofus can "propose" things. Fortunately most of these proposals are not taken too seriously, since the language keeps saying no to nearly all of them. The discussion is interesting though. My pet language extension to C would be closures (local functions that you can return pointers to, thus allocating memory for their local variables); I've seen several proposals for them come and go, without much success.
GCC has many really good extensions to the C language. Why have they not been adopted? Has nobody submitted them as proposals?
Sure. I'd say that most changes in the C language since 1989 have been first adopted by gcc as extensions, way before they were officially approved.
These refer to variables that exist in memory when the program is running:

  const int a;
  extern int b;
  static int c;
  volatile int d;
And this refers to something that exists only at compile time:

  constexpr int z = 1;
This ain't right.

Not that C doesn't need compile-time elements, it'd be better off not copying them from C++ verbatim and implementing in a way that doesn't introduce confusion into basic language concepts such as variables and functions.

I’m really not seeing what the issue is here. Could you expand on it?

As a programmer why do I have to reason or care whether something is compile time only or not? I don’t worry about constant folding but it is a similar thing.

Proposed constexpr syntax mimics that of a conventional variable, yet it doesn't create one and comes with distinctly different usage semantics. It's a different language element, I see no reason why it should be bundled up with another one.
Conventional variable syntax may not create a variable either. The compiler elides as it can or sees fit. This is my point, you don’t reason about these details today, why should constexpr be different?
Because constexpr and variables are not the same thing? E.g. with a variable you can still always take its address. With constexpr you can't.

Plus 'constexpr' is a patently ugly keyword. Also an important metric to consider :)

My point is that arguing about what is a variable is moot in the presence of compiler optimizations. You might think that taking and address makes a difference but if the compiler decides that it can elide it will (for example if the taken address never leaves the local scope and the variable is purely local then the compiler might be able to optimize it away).

Regarding ugliness… Idk beauty is in the eye of the beholder. I happen to like constexpr ;)

If you refer to that z somewhere, depending on its value and use it might exist as a separate value in the executable, as part of one or more instructions, or not at all (e.g. if you use that z only in if(z) phrases)

> This ain't right.

That’s an opinion that’s not universally shared.

One could similarly write

These refer to variables that can be changed in memory when the program is running:

  int a;
  extern int b;
  static int c;
  volatile int d;
And this refers to something that cannot be changed at runtime:

  const int z = 1;
This ain't right.

or variations on that theme with unsigned, values vs pointers, int vs long, etc. In all cases, that “this ain’t right” doesn’t logically follow. Why would yours?

  typedef int w;
This also refers to something that exists only at compile time. Where's the inconsistency?

constexpr makes a lot more sense as a storage class than typedef.

Does anybody have a real case study about the benefits of using constexpr? Everyone is raving about it but their examples of where to use it are absolutely trivial (e.g. return 1+2) and I can't really think of any real-world programs that would attempt to do such computations in a non-dynamic way.
Just as a starting point, you can look at any existing uses of #define to either define a constant or define a ‘function’ that can be evaluated at compile time.

For constants, #define is ubiquitous and works fairly well, but it has several disadvantages including:

- When a macro quotes some code, e.g. when assert() shows the expression that failed, any uses of macros inside that code will show up fully expanded. This makes it harder to tell what’s going on.

- If the definition of the constant has any complexity to it, it has to be re-evaluated every time it’s used.

- Doesn’t work with debuggers in some cases (admittedly this could be fixed without a new language feature).

- Difficult to work with for tools that generate FFI bindings for other languages. They can’t easily tell what’s a constant that should have bindings generated for it, versus what’s some random piece of syntax.

An alternative way to define constants in C is with anonymous enum declarations; they avoid all of the above issues, but they can only create constants of integer type – not structs or floating point values – and on some compilers are even more restricted to only creating constants of type `int` or `unsigned int`.

For functions: There are many function-like macros in C APIs. Some must be macros because they're type-generic or otherwise go beyond what a regular function can do. Others could work just as well as inline functions, even in current C, and are macros only because they're inherited from days when compilers couldn't reliably inline functions.

But others are macros because they're meant to be evaluated inside compile-time constants.

For example, from OpenBSD's implementation of the Unix socket API:

    #define CMSG_SPACE(len) (_ALIGN(sizeof(struct cmsghdr)) + _ALIGN(len))

    #define _ALIGN(p) (((unsigned long)(p) + _ALIGNBYTES) & ~_ALIGNBYTES)
CMSG_SPACE calculates the required buffer size for a control message structure of a given body size. One of the examples from the relevant man page [1] has:

    union {
     struct cmsghdr hdr;
     unsigned char buf[CMSG_SPACE(sizeof(int))];
    } cmsgbuf;
which requires CMSG_SPACE(sizeof(int)) to be a compile-time constant.

Mind you, this is all just a starting point. There are more ambitious things you can do with constexpr that people aren't doing with #define because it's not powerful enough. For example: if you use printf, modern compilers have built-in functionality to validate the format string against the argument types. But what if you want to define your own variadic function (for formatting or any other purpose) with a different format string mini-language? With constexpr you can do your own validation at compile time. (It's a bit easier in C++, but it should be doable in C as well if constexpr is added, by using a wrapper macro.)

[1] https://man.openbsd.org/CMSG_DATA.3

This would be something useful, so the committee won't have it.
The only thing I really want in C is the gradual removal of undefined behavior.

Edit: C isn't perfect, but the biggest usability issues is the subtle footguns from UB. Everything else pales in comparison.

Completely agree! I think C99 is the ultimate version of C, newer standards should be about defining previously undefined behavior and removing deprecated stuff that should not be used like gets and variable length arrays.
You might like the way ImportC does it,then. There is no change to the grammar, no new keywords. The obvious stuff just works, whereas in C11 it fails.
D's ImportC compiler already does this:

https://dlang.org/spec/importc.html#ctfe

It's a natural consequence of using D's semantic analysis for the C compiler.

I thought about removing it, because it's C, but then decided Vot De Heck and left it in. It's quite nice for the test suite for ImportC, too, as an executable doesn't have to be generated to check the semantics.

P.S. No keyword is required to make it work.

P.P.S. ImportC also allows forward references to declarations, another consequence of using D's semantic implementation.

Here's ImportC in action:

    _Static_assert(square(2) == 4, "failed");

    int square(int x) { return x * 2; }
1. there is no need for constexpr keyword. Anywhere a constant-expression in the C grammar appears, compile time function execution is involved.

2. `square` is forward referenced, but works fine.

Hmm, isn't square normally x*x?
Mr. Bright is just using TDD. The ‘square’ function works for 2. Next he’ll generalize it to work for more values. Tests aren’t shown. ;)
(comment deleted)
I tested it, it worked for the one and only test case, therefore it was correct :-)
Walter actually retired years ago and replaced himself with an AI. Every now and again there are bugs, move along.
It's always a pleasure seeing your comments when it comes to language design. I noticed the spec currently says that _Atomic is ignored. Is that a current limitation of the implementation, or is it not planned to ever be implemented? I can see that breaking real C code in surprising ways.

Other than that, I can't wait to use ImportC in my own D projects, I have a couple cases where it would simplify builds dramatically.

_Atomic is a problem because it doesn't map onto any existing D semantics. D's `shared` is distinctly different. So I decided to move it down on the list of priorities, instead focusing on what I can get done in the shortest time.

I'll eventually circle around to it, or someone may beat me to it. It is open source, after all!