Hacker News new | past | comments | ask | show | jobs | submit login

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?") :)




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

Why on earth would you do this?


> 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 :)


Good way to spend the weekend, I totally get it :) I was thinking that constexpr has been around since 11 (17 has if-constexpr, but I didn't see any of those). Were there particular features of 17 that you were able to benefit from?


While constexpr was introduced in C++11, it was initially very limited -- a constexpr function could only consist of a single return statement, for example. It was only in C++14 that things like variable declarations and loops were allowed, and constexpr programming became practical.

> Were there particular features of 17 that you were able to benefit from?

I used std::variant from C++17 for polymorphism, as a workaround for the fact that you can't have constexpr virtual functions (these have since been proposed for C++20). I also used `std::optional` when testing for intersections between a ray and a "thing", but it would have been possible to structure the code in such a way that this wasn't necessary (for example, by using a bool return value and an out parameter).

Lastly, I used constexpr lambdas (new in C++17) though again, it would have been possible to do things differently so these weren't required -- and indeed I did have to work around the fact that Clang (at the time) didn't support evaluating capturing lambdas at compile-time.


Regarding using std::variant for polymorphism as a work-around for not having virtual functions: I've often thought there's a bit of a mismatch between how many different derived classes (0 to infinity) an abstract base class can support, versus how many are actually needed in the average user code (anecdotally, 2 or 3 real classes and however many mocks needed for unit tests). Even something like a GUI framework may still have a manageable amount to just compile into something like a std::variant. Theoretically a whole-program analysis could even compile language-level inheritance into something like a std::variant. My point is, this mismatch between how flexible the technique we're using is versus how much flexibility we actually need, maybe indicates that we're not seeing something about how we develop software.

An analogy would be if we used only ball-and-socket joints everywhere in machines, even where a single-axis hinge would work just fine. And then just accepted that machines are inherently wobbly, rather than questioning whether extra degrees of freedom (whether needed or not) are always better than less.

I think sometimes what we really want when we use class inheritance is actually something like a std::variant where we don't have to know all of the constituent types in advance.


> My point is, this mismatch between how flexible the technique we're using is versus how much flexibility we actually need

well, most of the software I use every day have some sort of plug-in interface which is done at some point by loading a .dll at runtime and getting derived instances of a base type defined in the host app.


That's a very good point. It might be question of high versus low level. From a video I watched today (the rest of the video is worth watching, too, but the following stand-alone quote is apropos): "Polymorphism and interfaces have to be kept under control. I don't believe that their role is in a very low level system like interpolating a couple of numbers in the animations, but they have their place in high level systems, in client facing APIs. They are amazing if you do a type of plug in and you really need some type of dynamic polymorphism."

https://youtu.be/yy8jQgmhbAU?list=WL&t=2553


For sure, this is one of the legitimate uses of runtime vtable-based polymorphism. But it's also unnecessary if you assume the user is able to recompile the program.


Great explanation, thank you!




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: