RSS Amplifier

Hot Path · Mar 18, 2026

The Compiler as a Physics Engine

0
Sign in to vote or save

Robert Geil · Hot Path

In our first two posts in this series, we discussed the need for strong types in C++ to improve type safety and better document APIs, all while adding zero runtime cost to our software beyond using simple primitives. We first defined these strong types, then added traits to describe the permitted operations on them using CRTP. From here though, we can hook further into C++’s compile time system to perform changes on the strong types as we compute, permitting us to write code that changes types, preventing incompatible type operations and generating new types on the fly.

Recall back to our first example, to compute the new position of some object after an elapsed period of time, using just our initial strong types

With our CRTP strong types, we were able to define Addable, Subtractable and Multiplyable traits on each of these types, but without further changes, they cannot be inter-operated. For example, a strong type Velocity cannot be multiplied by a strong type Time since they are different strong types. Clearly this is one of the basic goals of first creating strong types, making it so we can’t mix operations on different types. Additionally, what would the type of the output of (Velocity * Time) be? It shouldn’t be either velocity or time, it should be a new type of Distance as described by physics. Let’s now work through how we can implement the creation of this new type and enable our C++ compiler to understand physics computations.

In order to represent our determine_future_position function with physically correct computations, we first need to take a step back and consider in the real world what each of these types actually represents. We’ll be using the SI units m to represent meters, s to represent seconds. As such we have

\(\text{distance} = m\)

\(\text{velocity} = m*s^{-1}\)

\(\text{acceleration} = m*s^{-2}\)

With this representation, we see our “types” for distance, velocity and acceleration actually have 2 dimensions that need to be represented, time and distance. Since these types are differentiated by the exponents of their units, conceptually we need to encode both the units and their exponents into our corresponding C++ types. Using the ability to template on a non-type parameter such as int, we can define our initial representation to be a templated type Dimension with template parameters to represent the exponent of both Meters and Seconds1

This allows us to create our above types as aliases

Going back to our strong types, since those were templated on a type T for their tag, we can now template our strong types on a Dimension as their tag. Additionally, we will make our types Addable and Subtractable since we should support operations such as adding together two velocities, or subtracting two distances. With some additional type aliases, we now have the following

We can now express our addition and subtraction on a single physics type cleanly without dropping down to the underlying type, but still don’t have the ability to do cross-type operations. Since we want our types to be able to be multiplied and divided, but not return the same type, we’re going to define our operator* and operator/ externally, simply performing multiplication and division on our underlying values.

These functions perform our multiply and divide operations on two arbitrary physics types, but I’ve explicitly left the resulting type as some unknown quantity type. To determine what that resulting type is going to be, we’re going to need to do some compile-time math.

We’re going to first extend our Dimension type we created above to allow easier access to the non-type template parameters in expressions, and then define a struct that exposes a type alias for when two dimensions are multiplied or divided

With these new helper structs, we can validate at compile time that our dimensional operations work as expected

The static asserts all pass indicating that our dimensional multiplication and division is indeed creating the correct types.

Now all we need to do is to plug that into our multiplication and division functions for the tag on the strong type and we should be good to go.

Since we’ve defined our dimensional multiplication and division, we are now able to fully write out our determine future position function without ever using the underlying types

By writing our multiplication and division operators to return new types, we’re able to fully express our mathematical equation without resorting to using primitive operations. Of course, we still have the exact same assembly output for this function as when we used primitive types, since all the code to generate the new types is executed at compile time.

Furthermore, the compiler now checks us to ensure we’re not performing invalid operations. For example, if we made a mistake in our function, such as the following

We get a fantastic error message telling us explicitly that we cannot add ‘Distance’ and ‘Velocity’.

One of the other powerful features of this technique is that we can create new types for the compiler without ever having to write them out in code. We never defined ‘Jerk’ (the first derivative of acceleration) as a concept, but if we run the following code, we can have the compiler generate jerk as a type.

With our dimensional types, we’ve showed how we can encode physical concepts into our C++ compiler, allowing us to further utilize strong types and generate safer code with zero overhead. This can also be extended into many other domains, such as encoding options greek exposures with concepts like delta and theta being encoded as dimensional quantities with respect to the underlying or time respectively.

A huge thank you to anyone who read through this first series of articles detailing strong types and their extensions. Future posts will likely cover more performance related concepts as well as software engineering principles. If you’ve made it this far, please consider subscribing to stay up to date on all new posts. Thank you for reading, until next time!

1

Note we could use the full set of SI units and include mass, current, luminosity, temperature and substance, but we’re keeping it simple for this article. It would be straightforward to add these additional units following the same framework laid out here. Please also check out Nic Holthaus’ fantastic units library which utilizes and expands far beyond what this post describes

No posts

Read the original on hotpath.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.