Proposal to add constexpr to C [pdf]
open-std.org
open-std.org
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.
_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.
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.
I'll eventually circle around to it, or someone may beat me to it. It is open source, after all!
Is this not what Blocks are? They are common on Apple platforms.
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.
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.Yes, that's what I meant with "the compiler can do it".
Heavy type inference, overloading, and inheritance are a completely different beast and they make the code a lot more opaque.
const int x = const foo(bar(y));
?Unfortunately, there is a big political war ongoing, so instead C++ keeps collecting const.... keywords.
They could have just used "pure" like in D, but this is C++, so that would be too sane and obvious.
It's also why they reuse keywords like `using`.
Edit, typo: I meant "You don't want every const-able method to be const."
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.
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.
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 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.
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...
Unfortunately it isn't a direction WG21 is willing to adopt.
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.
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.
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.
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.)
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.
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.
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
See this code:
>> [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.
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.
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.
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.
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...> 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.
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 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.
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.
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.
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.
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.
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.
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).
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.
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);For example, "if constexpr(is_constant_evaluated())" is always true, I think.
See also the new C++ keyword "consteval" (yes, really!)
what do you think that I think that constexpr does ? constexpr in a variable forces the variable's value to be evaluated in a constant expression context (which comes with footgunes, see an in-depth coverage of the issue there: https://gist.github.com/Som1Lse/5309b114accc086d24b842fd803b...)
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.
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
> 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.
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.
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.
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.
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.
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.Moreover I can't even see how I could've mentioned lambdas. They have a superfluous keyword? Which one?
>> 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!
MUTEX_LOCK_GUARD(&lock)
for (i = 0; i < n; i++)
if (a[i] == x)
return i;
return -1;
Real world example here: https://github.com/qemu/qemu/blob/master/block/iscsi.c#L325And the implementation: https://github.com/qemu/qemu/blob/master/include/qemu/lockab... (after inlining, the abstraction is completely zero cost).
So you can forget about compilers caring to improve the quality of an optional feature, that was even cleaned from Linux kernel.
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).
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.
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
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.
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.
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.
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.
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.
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.
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.
> 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.
Being able to explicitly tell the compiler, "you can and should compile this out" seems like a good thing?
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.
Edit: C isn't perfect, but the biggest usability issues is the subtle footguns from UB. Everything else pales in comparison.
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.
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.
Plus 'constexpr' is a patently ugly keyword. Also an important metric to consider :)
Regarding ugliness… Idk beauty is in the eye of the beholder. I happen to like constexpr ;)
> 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.
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.
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.
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.)