[HN Gopher] Proposal to add constexpr to C [pdf]
___________________________________________________________________
Proposal to add constexpr to C [pdf]
Author : pantalaimon
Score : 69 points
Date : 2021-10-14 08:10 UTC (2 days ago)
(HTM) web link (www.open-std.org)
(TXT) w3m dump (www.open-std.org)
| JustAnonymous wrote:
| This would be something useful, so the committee won't have it.
| xvilka wrote:
| They should add `pure` function attribute instead, also maybe the
| ownership.
| nuerow wrote:
| > * 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.
| Ginden wrote:
| 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.
| elteto wrote:
| 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.
| WalterBright wrote:
| 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.
| WalterBright wrote:
| 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.
| ori_b wrote:
| 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.
| matheusmoreira wrote:
| 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.
| eps wrote:
| 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.
| moonchild wrote:
| 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.
| elteto wrote:
| 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.
| eps wrote:
| 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.
| elteto wrote:
| 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?
| huhtenberg wrote:
| 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 :)
| elteto wrote:
| 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 ;)
| Someone wrote:
| 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?
| colonwqbang wrote:
| 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.
| zabzonk wrote:
| > 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.
| jstimpfle wrote:
| 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.
| pantalaimon wrote:
| > 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.
| jstimpfle wrote:
| 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.
| sgtnoodle wrote:
| 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?
| klyrs wrote:
| 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.
| [deleted]
| nly wrote:
| 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.
| nuerow wrote:
| > _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.
| nly wrote:
| > 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.
| RicardoLuis0 wrote:
| have you considered using C11's _Generic feature? It allows you
| to maunally implement function overloading:
| https://godbolt.org/z/jxjvnMTPT
| saurik wrote:
| 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.
| dataflow wrote:
| > 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.
| secondcoming wrote:
| 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.
| nuerow wrote:
| > _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.
| dataflow wrote:
| > 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.
| nuerow wrote:
| > 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.
| dataflow wrote:
| > 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.)
| thezilch wrote:
| What? https://gcc.godbolt.org/z/EzrWW9ss5
| [deleted]
| dataflow wrote:
| 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.
| thezilch wrote:
| 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.
| dataflow wrote:
| > 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.
| pjmlp wrote:
| It can, check the C++ compiler Circle.
|
| https://www.circle-lang.org
|
| Unfortunately it isn't a direction WG21 is willing to adopt.
| wtetzner wrote:
| I just assumed the constexpr keyword was added to help the
| programmer ensure that what they were writing could be run
| at compile time.
| rowanG077 wrote:
| 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.
| nuerow wrote:
| > _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.
| saagarjha wrote:
| Not every C compiler is a C++ compiler.
| NavinF wrote:
| 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.
| dataflow wrote:
| > 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.
| NavinF wrote:
| 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.
| dataflow wrote:
| > 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).
| manwe150 wrote:
| 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.
| [deleted]
| jcelerier wrote:
| > 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).
| dataflow wrote:
| > 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.
| jcelerier wrote:
| 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, a la template<auto
| Value> struct manually_constexpr { static constexpr
| auto value = Value; };
| CHECK(manually_constexpr<my_algo()>::value == 123);
| petters wrote:
| "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!)
| jcelerier wrote:
| ah yes, that'll teach me to type on my phone :)
|
| 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/5309b114accc086d24
| b842fd803b...)
| dataflow wrote:
| > 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, a 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.
| jcelerier wrote:
| > 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
| dataflow wrote:
| >> 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.
| jcelerier wrote:
| > 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.
| dataflow wrote:
| > 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
| jcelerier wrote:
| > "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.
| dataflow wrote:
| > 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.
| AlexanderDhoore wrote:
| 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.
| pjmlp wrote:
| 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.
| voldacar wrote:
| "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.
| pjmlp wrote:
| Politics, check the outcome of the networking vote.
| hmfrh wrote:
| 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`.
| kevin_thibedeau wrote:
| They could just use the reserved keyword naming like the
| C committee does.
| nly wrote:
| 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.
| pjmlp wrote:
| There is a video about the story background, where a
| group of kids play the role of the committee voting down
| Circle.
| nly wrote:
| Link?
| pjmlp wrote:
| Enjoy, https://m.youtube.com/watch?t=2035&v=NNU6cbG96M4
| dataflow wrote:
| 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.
| dataflow wrote:
| > 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. "
| zarzavat wrote:
| 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.
| nine_k wrote:
| Why not const int x = const foo(bar(y));
| ?
| mpyne wrote:
| 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)
| AlexanderDhoore wrote:
| "inspect the definition"
|
| Yes, that's what I meant with "the compiler can do it".
| zabzonk wrote:
| Not given C and C++ separate file compilation.
| zarzavat wrote:
| 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.
| foxfluff wrote:
| 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.
| [deleted]
| SuchAnonMuchWow wrote:
| You should check Zig comptime keyword, which does exactly
| that. Its the feature from zig I miss the most in C.
| User23 wrote:
| That sounds like a subset of EVAL-WHEN[1].
|
| [1] http://clhs.lisp.se/Body/s_eval_w.htm
| dataflow wrote:
| 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.
| NavinF wrote:
| 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.
| dataflow wrote:
| 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.
| comex wrote:
| 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.
| dataflow wrote:
| > 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.
| electroly wrote:
| 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.
| dataflow wrote:
| 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.
| electroly wrote:
| 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.
| dataflow wrote:
| 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.
| boulos wrote:
| ... 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).
| dataflow wrote:
| > 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.
| marcodiego wrote:
| 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.
| dataflow wrote:
| 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?
| dataflow wrote:
| 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.)
| huhtenberg wrote:
| 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.
| bonzini wrote:
| 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).
| enriquto wrote:
| 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).
| bonzini wrote:
| How would variable length arrays, for example, help with
| releasing a mutex on early returns from a function?
| enriquto wrote:
| They wouldn't, of course! Releasing resources silently
| (other than the memory of automatic variables) is very
| un-C-like.
| dataflow wrote:
| > 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!
| ulnarkressty wrote:
| 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.
| comex wrote:
| 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
| rdpintqogeogsaa wrote:
| This is at most tangentially related, but I could never figure
| out how to submit proposals for the C standard. How does this
| work?
| dcminter wrote:
| Partial answer I guess? http://www.open-
| std.org/JTC1/SC22/WG14/www/contributing
| enriquto wrote:
| 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.
| matheusmoreira wrote:
| GCC has many really good extensions to the C language. Why
| have they not been adopted? Has nobody submitted them as
| proposals?
| rgovostes wrote:
| > My pet language extension to C would be closures
|
| Is this not what Blocks are? They are common on Apple
| platforms.
|
| https://en.wikipedia.org/wiki/Blocks_(C_language_extension)
___________________________________________________________________
(page generated 2021-10-16 23:01 UTC)