[HN Gopher] Constexpr is a Platform (2020)
       ___________________________________________________________________
        
       Constexpr is a Platform (2020)
        
       Author : Sindisil
       Score  : 89 points
       Date   : 2021-08-08 17:53 UTC (1 days ago)
        
 (HTM) web link (foonathan.net)
 (TXT) w3m dump (foonathan.net)
        
       | qalmakka wrote:
       | With constexpr and not consteval you can really do crazy stuff at
       | compile time. It's super neat and it has eradicated macros
       | everywhere in my code, except in those places where I need
       | conditional compilation.
        
         | jahnu wrote:
         | I really wonder why I'm finding very few uses of constexpr in
         | my code. I feel like I'm missing out on something important but
         | just can't find places where it works for me.
        
           | qalmakka wrote:
           | Here's some examples:
           | 
           | 1. Declare all your constants as constexpr. They can then be
           | used everywhere in constexpr functions, and every time you
           | invoke a constexpr function with constexpr arguments or
           | literals there's a very high chance it will be executed at
           | runtime. You can make expressions clearer and express stuff
           | with functions, without paying the cost of the invocation.
           | For instance, Intellisense and Clangd often show the value of
           | very complex expressions on hover.
           | 
           | 2. `if constexpr` is very effective to execute code
           | conditionally in functions or lambdas with `auto` parameters,
           | you just need to combine it with `std::same_as` or
           | `std::is_same_v`.
           | 
           | This is immensely useful with std::variant and std::visit,
           | for instance.
        
             | secondcoming wrote:
             | Yes. Just last week I replaced a std::enable_if that need 3
             | functions with one function that used 'if constexpr'. I'm
             | sure I'll find more places soon.
        
       | omoikane wrote:
       | See also: https://github.com/keiichiw/constexpr-8cc
       | 
       | This tool embeds a C compiler as a constexpr, so in theory you
       | can do all the work at compile time and just output a static
       | string at run time. But currently I am having trouble getting it
       | to work, so it's perhaps not so glorious other than the amount of
       | time it takes to compile.
        
         | rkeene2 wrote:
         | ELVM [0] also has a C++14 constexpr target, also using 8cc as
         | the frontend !
         | 
         | [0] https://github.com/shinh/elvm
        
       | WalterBright wrote:
       | > we could imagine a future version of C++ where this [constexpr
       | annotation] isn't required: if we're calling a function at
       | compile-time, the compiler tries to execute it at compile-time.
       | If it works, good, otherwise, it issues a diagnostic.
       | 
       | This is how D's CTFE (Compile Time Function Execution) works. You
       | can execute at compile time any function, and it will work as
       | long as the source code is available to the compiler, and it
       | doesn't access globals or do pointer monkey business.
       | 
       | You can indeed build a program that runs completely at compiler
       | time. Here's a compile time ray tracer in D circa 2006:
       | 
       | http://h3.gd/posts/ctrace/
        
         | titzer wrote:
         | Virgil initializers are run at compile time and can literally
         | run any code in the program to build up any data structures you
         | want. The result of initialization is the transitive closure of
         | objects reachable from the globals. The compiler goes further
         | and optimizes your code against the initialized heap,
         | devirtualizing, inlining, and constant-folding, throwing away
         | dead code, dead fields, and dead objects. That's great for
         | microcontrollers because you can just allocate your entire heap
         | at compile time and then bake it into your image.
         | 
         | I wrote a paper about it in 2006:
         | https://escholarship.org/uc/item/13r0q4fc
         | 
         | I still work (primarily, in fact) on Virgil. It's great not
         | having any distinction between compile-time initialization and
         | runtime.
        
         | omegalulw wrote:
         | I don't see the value TBH. constexpr is excellent for code
         | readability. Even if the compiler could figure it out I'd vote
         | to keep it. That said, if compiler could figure it out than you
         | can probably set up your editor to annotate it.
        
           | SubjectToChange wrote:
           | I don't see how constexpr is helpful for readability. As it
           | stands, constexpr adds a significant amount of syntactic
           | noise. D's approach is the correct one, IMO.
        
           | WalterBright wrote:
           | As the article points out, an entire function doesn't need to
           | be CTFE compatible, only the particular path taken through
           | the function by the interpreter.
           | 
           | This makes constexpr more of an impediment than an enabler.
           | 
           | Besides, people soon discover the utility of using functions
           | at both compile and run time, and nobody has ever asked for D
           | to require an annotation for it in 15 years.
        
         | codetrotter wrote:
         | Jonathan Blow's programming language allows almost anything to
         | execute during compilation as well. It's neat. Here's an old
         | video where he demonstrated this: https://youtu.be/UTqZNujQOlA
        
           | WalterBright wrote:
           | That's true. His implementation also allows compile time
           | function execution to make system calls.
           | 
           | D disallows that in order to prevent malicious source code
           | from being distributed and tricking people into compiling it.
           | There is a mechanism to sent output to stdout, input can be
           | read from a file residing on the compiler's system, and
           | that's it.
           | 
           | It's also why CTFE doesn't allow pointer monkey business,
           | like coercing an integer into a pointer and dereferencing it.
        
             | SubjectToChange wrote:
             | If you're compiling malicious code, haven't you already
             | lost? I don't see how preventing pointer manipulation at
             | compile time (or anything else, really) improves security
             | unless people never run the executable they compiled.
        
               | WalterBright wrote:
               | People expect to check source code before running it.
               | They do not expect to have to check it before compiling
               | it.
               | 
               | Malware works by taking advantage of peoples'
               | expectations.
        
               | gpderetta wrote:
               | Given the way modern IDEs work, unrestricted compile time
               | evaluation would make it dangerous to even open
               | potentially malicious code.
        
               | SubjectToChange wrote:
               | _People expect to check source code before running it.
               | They do not expect to have to check it before compiling
               | it._
               | 
               | Why are they compiling untrusted code? To see if it's
               | syntactically correct? If the code you're compiling is
               | using a build system, you're already compromised.
               | 
               | Compilation of untrusted code should always be sandboxed,
               | regardless of compile-time programming capabilities.
        
               | lalaithion wrote:
               | It means that you need to do                   $ sandbox
               | "foobarc malicious.foobar -o executable && ./executable"
               | 
               | instead of                   $ foobarc malicious.foobar
               | -o executable         $ sandbox ./executable
               | 
               | Which isn't totally intuitive.
        
               | WalterBright wrote:
               | Yes, I don't really want to have to recommend to people
               | that they have to run the compiler in a sandbox.
        
               | SubjectToChange wrote:
               | And what if you are compiling more than a single file?
               | What if the project or library you are compiling uses
               | make/cmake/gn/etc?
               | 
               | The point is that restrictions on compile-time
               | programming only protect you in trivial cases. And I
               | can't think of many reasons to fully compile code without
               | the intent of running the executable.
        
               | WalterBright wrote:
               | If you want a compiler that is designed to enable
               | ransomware injection into your system merely by compiling
               | a file, that's fine with me. But it won't be D.
        
               | usefulcat wrote:
               | Testing. If you wanted to test that your compile-time
               | code is correct, I'd imagine the typical--if not only--
               | way to do that would be to compile it. Certainly that's
               | true for constexpr in c++.
        
               | johannes1234321 wrote:
               | you could compile it to verify, but you usally compile it
               | using a build system to set include paths right etc. that
               | build system could include a `rm -rf $HOME 2>/dev/null`
               | or similar already.
        
               | SubjectToChange wrote:
               | Testing code requires executing it. This is true for
               | compile-time code or run-time code.
        
         | kubb wrote:
         | What a novel idea, I'm sure there wasn't a language called lisp
         | that could do that 50 years ago.
        
           | [deleted]
        
           | WalterBright wrote:
           | Lisp does deserve credit for being first. But didn't Lisp do
           | it the other way around - interpreter first, later add native
           | code generation?
           | 
           | Before D, people discovered that C++ templates could be used
           | for compile time execution. Everyone was excited about this.
           | The book that revolutionized C++ programming by exploiting
           | this was Andrei Alexandrescu's book "Modern C++ Design"
           | published in 2001.
           | 
           | But as far as I knew, nobody ever wrote about executing
           | regular C or C++ functions at compile time in a native
           | compiler. (There were some C interpreters, but they were
           | interpreters only.) Once D showed it could be done, and how
           | useful it was, the rush was on for other curly braced
           | languages to do it.
        
             | secondcoming wrote:
             | > Andrei Alexandrescu
             | 
             | Who's that guy?
        
             | moonchild wrote:
             | It's possible that very early lisps were interpreted--I'm
             | not sure--but lisp had been a predominantly compiled
             | language for many years before c++ arrived on the scene.
        
       | nonsince wrote:
       | This is the fundamental observation that the Zig language is
       | built on, and it proves to be an extremely powerful way of
       | understanding your system when taken to its logical extreme.
        
         | dnautics wrote:
         | While the (2-3 paragaphs-in observation) is common between the
         | post and zig, I wouldn't go so far as to call comptime a
         | "platform" in zig, it is a very different class of thing than
         | say arch/os/abi.
        
       | catern wrote:
       | The analogy is fun, but I'm not sure I agree with the author's
       | conclusion:
       | 
       | > a constexpr marker should be unnecessary, just like a
       | hypothetical linux marker
       | 
       | In fact, I'd love to have a "linux" annotation for functions
       | which can only be called on Linux! Using the type system to
       | manage the context in which a function can be called is a good
       | idea, and allows safer code.
       | 
       | The alternative is global type inference (which is slow) or
       | runtime failures (which defeats the point).
        
         | runevault wrote:
         | I agree. He made a point that, if I understood I agree with,
         | that constexpr is not necessarily as strict as it should be.
         | Specifically that you only need ONE path to work at compile
         | time to be able to be constexpr. I might be missing a reason
         | this is a good idea but at least to me it seems bizarre.
         | 
         | It would 100% be nice if code could be tagged by context in
         | more ways than constexpr so the compiler can better tell you
         | things like "put this behind an ifdef for Linux" etc (maybe
         | with a compiler option to say this will only ever target one
         | context and you don't need to check for that).
        
         | tialaramex wrote:
         | In Rust you can tag a function with arbitrary configuration
         | parameters, it's common to write
         | 
         | #[cfg(unix)]
         | 
         | but you could equally write
         | 
         | #[cfg(linux)]
         | 
         | It's just that you're much less likely to _need_ to say this.
         | For example OSStr (borrowed reference to the Operating System
         | specific string type) has #[cfg(unix)] reflecting APIs that
         | make sense for a Unix-style string while #[cfg(windows)] wraps
         | APIs that make sense if your operating system once thought
         | characters are a u16. But the ordinary str and String don 't
         | have such APIs, you have to explicitly tell the language "I
         | need this gross non-portable stuff" before you have to start
         | using macros to get to the specific platform dependant
         | elements.
         | 
         | I suspect you could do a similar trick in C++ but it may not be
         | standardised anywhere.
        
           | pornel wrote:
           | Moreover, Rust is working on portability lints:
           | 
           | https://github.com/rust-
           | lang/rfcs/blob/master/text/1868-port...
           | 
           | So that even when you only compile and test your code on
           | Linux, you will get warnings about calling Linux-only
           | functions from maybe-Windows functions.
        
             | comex wrote:
             | For some definition of "working on". The RFC was accepted
             | several years ago, but nobody has stepped forward to
             | actually implement it.
        
         | klodolph wrote:
         | I thought the author's conclusion was that constexpr
         | annotations were necessary?
        
           | catern wrote:
           | No, they quite clearly say the opposite, I edited my comment
           | to quote the post.
        
         | pjc50 wrote:
         | You can do that in C# : https://docs.microsoft.com/en-
         | us/dotnet/api/system.runtime.v...
         | 
         | (Strictly it's an attribute rather than a type, but it's
         | enforced by the compiler given a target runtime)
        
       | jeffrallen wrote:
       | This article is excellent ammunition for people who don't think
       | C++ is good for our industry. We need fewer things to reason
       | about while programming, not more "platforms" to reason about
       | concurrently.
        
         | VHRanger wrote:
         | Platforms are good
         | 
         | It's on your team culture to enforce good standards of what not
         | to use.
         | 
         | You can just as easily write a clusterfuck in Python (it's a
         | huge language!) as in C++. Python just has a good overall
         | culture of "what is pythonic" that helps this matter.
        
       | tialaramex wrote:
       | I originally thought this essay would be about the fact that
       | something like constexpr should appear to be the _same_ platform
       | as your target.
       | 
       | This is an interesting property. For example, if I insist on
       | writing down a 64-bit integer in memory and then staring at the
       | individual bytes, the _platform_ determines the layout of that
       | structure. You don 't want to know what the layout is for your
       | _compiler_ but for your _target_.
       | 
       | Floating point maths is often tricky in this world. You expect
       | the constexpr platform to have the same behaviour as your target,
       | but emulating that (assuming you aren't in the trivial case of
       | compiling _on_ the target) can mean some pretty fearsome work
       | that can 't be justified. So, maybe the ARM CPU at runtime
       | calculates a _very_ slightly different value for the  "constant"
       | internal_damping_factor from the value used in constexpr
       | functions on this x86-64 cross compiler...
       | 
       | However it turns out I once again underestimated how far out C++
       | wants to go here. Shine on you crazy diamond.
        
         | rowanG077 wrote:
         | Floating point can be easily handled as long as the compiler
         | says this is the way cte floats are handled irrespective of
         | platform. It only gets harder if you must match the target
         | platform. And even that could be pretty easily done with qemu
         | for most platforms I'd wager.
        
       ___________________________________________________________________
       (page generated 2021-08-09 23:01 UTC)