Constexpr is a Platform (2020)
foonathan.net
foonathan.net
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:
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.
Malware works by taking advantage of peoples' expectations.
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.
$ sandbox "foobarc malicious.foobar -o executable && ./executable"
instead of $ foobarc malicious.foobar -o executable
$ sandbox ./executable
Which isn't totally intuitive.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.
To test ImportC, which inherits D's ability to execute functions at compile time, I've been using a lot of _Static_assert() constructs.
https://github.com/dlang/dmd/blob/a5b16bd8e72a447d509cca862e...
It doesn't make sense to produce an executable, not execute it, then go audit the code. Just audit the code in the first place. And like I mentioned earlier, virtually no project is purely static code. They include shell scripts, make files, and tons of other things. Working with untrusted code is dangerous, period. Making compile-time programming a little safer is just passing the buck.
If you're auditing the code by examining it with an IDE, it could certainly get compiled.
Again, I don't understand why you'd want to wait until you have an unwanted/untrusted executable to begin auditing code.
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.
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.
There are some issues with it, because many ISO members want compile time capabilities constrained by constexpr, instead of the complete language, like Circle compiler allows.
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.
> 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).
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).
#[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.
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.
(Strictly it's an attribute rather than a type, but it's enforced by the compiler given a target runtime)
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.
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.
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.