
C++17 constexpr Compile-time Ray Tracer (2017) - zone411
https://github.com/tcbrindle/raytracer.hpp
======
hhmc
There's seemingly a lot of negative responses to articles concerning constexpr
in modern C++ (you can see some here in this thread). I suspect this is
partially because there's a general outsized dislike of C++, and partially
because typically these articles focus on toy problems where the capacity to
run at compile time isn't actually beneficial, rather than cases where
constexpr can provide some real benefit towards production code.

Like any syntactic construct in a turing complete language constexpr presents
a convenience - in this case the ability to rectify compile time generated
data and runtime code paths/data. There are some practical cases I've seen
where it can be a real improvement to hygiene (i.e. you can replace a compile
time script/program generating data with one unified code base). e.g.

1\. Perfect hash generation. Rather than using a separate gperf run you can
generate your hashes during compilation of the relevant TU.

2\. Format string lookup tables. For fast formatting you can push an index and
positional arguments to a queue, and handle formatted string construction in
another thread. Broadly speaking lookup tables in general can be generated at
compile time rather than static data imported into code.

3\. Complex starting conditions for physics simulations. If you have, say,
some initial matrix that doesn't change between simulation runs - you can
exercise existing code to generate this matrix at compile time rather than
needing a separate process run or script.

These approaches are not without their downsides - particularly if the
translation units in question need to be recompiled often, or if you're using
whole program optimisation - but the benefit of keeping generated data in
lockstep with the code consuming it can be worthwhile.

(That being said, I personally find articles like this incredibly edifying.)

~~~
Arnt
4\. Things you want to not run at runtime. If you can demand of the compiler
that foo is run only at compile time, you can eliminate some performance
outliers, with good protection against accidental later regressions. (I
suppose this could apply to security too — anything run at compile time is
definitely not vulnerable to crafty user input from a malicious user.)

5\. My compiler is a rackmount somewhere; even if constexpr evaluation is
somewhat slower than runtime code in a particular case, I may prefer to use
the rackmount's plentiful power rather than the slim, light battery on the
device where the final code will run.

~~~
tcbrindle
6\. A constexpr function is guaranteed to have no undefined behaviour (when
evaluated at compile time, anyway)

------
tcbrindle
Author here. I wrote this on a rainy weekend last year and mostly forgot about
it, so it was a bit of a surprise to see it pop up on HN this morning!

Happy to answer any questions (including "why on earth would you do this?") :)

~~~
rptr_87
Thanks for the article. Seems nobody seems to have asked anything.. i'll
start...

Why on earth would you do this?

~~~
tcbrindle
> Why on earth would you do this?

Good question! :)

It actually started out as a learning exercise -- I didn't (and still don't)
know much about ray tracing, and so I thought it would be good to study a
simple example by translating it from a language I didn't know (TypeScript) to
one I was more familiar with.

This involved writing a simple vec3 class, and it seemed natural to make that
constexpr. Then as I went through I realised that many more functions could be
made constexpr too. And then if I hacked together some (admittedly very poor)
replacement maths functions, even more of it could be constexpr...

At this point, it became a challenge to see whether I could make the whole
thing run at compile-time. The only tricky part was avoiding virtual functions
\-- I'd originally naturally translated TypeScript "interface"s into C++
abstract classes. I ended up using two different approaches: firstly using
std::variant and std::visit (the any_thing class in the source code), and
secondly using a struct of function pointers (for the surfaces) -- because
while virtual calls aren't allowed in constexpr code, calls via a function
pointer are just fine.

In the end, I was pretty satisfied that I'd managed to push constexpr as far
as I could take it. I had intended to blog about it, but never got round to it
... and now 18 months later someone has saved me the job by posting it to HN
anyway :)

------
sischoel
There also was a talk at CppCon on compile time regular expressions in C++20:
[https://www.youtube.com/watch?v=QM3W36COnE4](https://www.youtube.com/watch?v=QM3W36COnE4)

------
oconnor663
> Believe it or not, this is actually decent performance

The _ARE YOU NOT ENTERTAINED_ of programming.

------
blux
Impressive! Interesting as a benchmark to test the performance of different
compiler implementations.

------
rq1
There is no misuse of anything. Stop being so presumptuous and shortsighted,
especially on _hacker_ news.

Is there some theoretical bounds or anything preventing the compilers to be
faster or is it just implementation/optimization details?

(GHC tends also to be quite slow.)

~~~
intea
C++ compilation is massively slowed down by the sheer existence of the
preprocessor. This could be drastically improved by the usage of modules which
are coming soon(tm).

Both MSVC and Clang already support them experimentally fwiw.

~~~
Koshkin
To be fair, what slows down compilation is not "the sheer existence" but the
extensive use of large and complex #include files.

~~~
geezerjay
Indeed. IIRC this topic is covered directly in Meyers' "Effective C++" book,
including the trick to use pointers + forward declarations in headers to avoid
including other headers.

------
jordigh
D did this a long time ago too.

[https://news.ycombinator.com/item?id=7530692](https://news.ycombinator.com/item?id=7530692)

[https://web.archive.org/web/20151113173822/http://h3.gd/ctra...](https://web.archive.org/web/20151113173822/http://h3.gd/ctrace/)

It should be noted that this was done in very old D, before a lot of compile-
time improvements that modern D has. I guess makes this feat more impressive
for the time, while the same exercise today should be more pedestrian.

It's really nice that D was built with this sort of application in mind
instead of having it added 30 years later. It makes the language feel far more
cohesive and inviting for this sort of thing.

If C++ were able to discard old features, I might not be so scared of going
back to it. The way it keeps growing without pruning does not make it feel
very inviting.

~~~
rrmm
The annoying thing about C++ is you need a big huge book to tell you which
features are safe and how to combine them and stay safe.

Maybe if they started defining a 'modern c++' subset that you could limit it
to via compiler flags and/or pragma's it would make life a bit easier when
starting new codebases.

Obviously you can always make a personal style choice on what to use, but then
you have to find one. Anyone have a favorite set of recommendations for c++
feature usage?

Also, if you start with 'X did this a long time ago.' You always end up in a
LISP-hole. :P

~~~
0-_-0
> Maybe if they started defining a 'modern c++' subset...

Here it is:
[https://github.com/isocpp/CppCoreGuidelines/blob/master/CppC...](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md)

~~~
metabagel
Great reference, although none of the hyperlinks on the page work for me. I
tried Firefox, Chrome, and Edge. I also tried all of the older tagged
versions.

~~~
0-_-0
For me neither, try this instead:

[https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines)

------
abhishekjha
Apparently it doesn't compile because of how the pow function is implemented.
I am not able to figure out what the issue is right now.

------
dzdt
Ye gods. And I thought build times were long enough already!

------
hughw
In awe.

------
paulhilbert
So this is basically misusing the compiler as an impractial script
interpreter?

~~~
gpm
I'm not sure if it counts as misuse, compile time evaluation is intended to be
used to do computation at compile time. But "an impractical script
interpreter" is certainly one way to view it.

------
tapirl
so, c++ becomes greater?

~~~
gnulinux
C++ has a whole scripting language embedded into itself. They're trying to
reinvent lisp, of course a much less efficient lisp.

~~~
jcelerier
> They're trying to reinvent lisp, of course a much less efficient lisp.

yes, please show us all those high performance libraries written in lisp

~~~
ur-whale
It's OK.

Lispers live in a parallel universe, where performance issues are mere
distractions from the higher level thinking.

~~~
gnulinux
Not every performance issue needs lower level optimization. Most performance
issues can be solved by (1) optimizing algorithms and (2) abstracting lower
level code in FFI C functions. C++ helps for 2 but not for 1 because if you
don't have a clear high-level understanding of your algorithm, system,
pipeline, it's harder to optimize or maintain that optimized code.

~~~
jcelerier
> C++ helps for 2 but not for 1 because if you don't have a clear high-level
> understanding of your algorithm, system, pipeline, it's harder to optimize
> or maintain that optimized code.

I strongly disagree. C++ has very nice tools for abstraction: higher-kinded
types, dependent types (to some extent), lambdas & function objects,
overloads, etc. It's a pain to design a data flow in C, but in C++ for
instance it can just be something like this :
[https://github.com/RaftLib/RaftLib/wiki/Linking-
Kernels](https://github.com/RaftLib/RaftLib/wiki/Linking-Kernels) or this :
[https://ericniebler.github.io/range-v3/](https://ericniebler.github.io/range-v3/).

------
sick_of_web_dev
Fun project, but C++ sure is getting more ugly and arcane by the day. I think
it's time it went away. I have great love for C but C++ has just always turned
me off.

~~~
jeffreyrogers
I felt this way too but was using C++ for work and have grown to like it. If
you can use only the newer language features it is actually quite pleasant.
You can avoid a lot of the pointer arithmetic and off by one errors that occur
in C. I didn't realize how productive I was in the language until I found
myself writing a small script I would normally have written in python in C++
instead.

