C++ is an expressive language, and one of its strongest features is the rich type system. With user defined types we get a powerful set of tools to express code concepts through types, helping to prevent bugs and make software more understandable. However this set of types can break down, especially when dealing with primitive integers, floats, or even higher level types that aren’t carefully disambiguated. We’ll go through how using these weakly defined types can cause issues in API design, derive some solutions to the problem, and show how those solutions can make our code more expressive, harder to break and just as performant when compiled with a reasonable set of optimization flags.
Consider this simple example where we have, as part of some physics library, the following function.
This should immediately raise red flags as an API design. Not only are there a large number of parameters, all of the parameters are easily swappable. Nothing in the language will complain if callers of this function do something like this
Unless the user of this API goes in and inspects the call signature carefully, they’ll have no warning about the fact that they inadvertently moved the initial time from arg0 to arg3. No warning, that is, until they run their code and see garbage values generated.
One of the first steps that the API writer could have done is to enable Clang Tidy’s bugprone-easily-swappable-parameters check. This check, in its default configuration, will flag if a function has two or more parameters that can be swapped with an implicit conversion. Indeed, Clang Tidy rejects our above example precisely for this issue.
As an aside, if you’re not currently using Clang Tidy for your projects, it is an incredibly powerful tool. In addition to checks like the swappable parameters, it has a whole host of patterns, bugs and inefficiencies that it can automatically diagnose and sometimes fix. Furthermore if a check doesn’t exist, the API is extensible to write new, custom checks that inspect the full AST. I will cover this in a future post.
Now that we have flagged this function as potentially problematic, what can we do about it? One approach would be to try and better define the types of these arguments we are passing in with a type alias.
While this is nice from a readability standpoint, since we’re no longer just relying on the argument names to describe what they are, it doesn’t get us any closer to resolving the core issue. Because type aliases in C++ are just that, aliases. In cppreference it states that “[a type alias] does not introduce a new type and it cannot change the meaning of an existing type name.” Essentially all we’ve done is created a new way to say “float” in our program. With this change Clang Tidy still rejects our code since we’re functionally outputting the exact same program with different aliases. Incorrectly ordered args in calling code for our API still passes all checks.
One way to redesign our API would be to package some of the parameters together into a struct that can be passed into the function.
This has indeed cleaned up our API and silenced Clang Tidy, however we’ve simply pushed the problem up a level and introduced potential other failure cases.
One potential issue with this new approach is we now no longer are requiring that all of position, velocity and acceleration are provided to our function. For example if we were to do something like the following, we could entirely omit one of these parameters without any compiler warnings at all.
Of course, we can enforce that the struct has all members populated by creating a constructor, so it’s impossible to incompletely initialize the struct
However we’re right back where we started, now with the constructor for KinematicState having 3 easily swappable parameters and again getting flagged by Clang Tidy.
Ideally in our program, Position, Velocity, Acceleration and TimePoint would all be unique types that cannot be interchanged with each other. As such, we need convenient way to create new types with minimal boilerplate, as well as an escape hatch to grab out our underlying data. All of this should also be zero runtime overhead, leaning into C++’s promise of zero overhead abstraction.
One way we can quickly create new types is by using C++ classes or structs to define a new type for each of our quantities. When working with user-defined types, C++ offers strong typing. If we have a class Foo and a class Bar, even if their implementation is identical we cannot assign a Foo to Bar, compare a Bar against a Foo or mix between them in any way, unless we explicitly write conversions or operators that take in mixed class types. We’ll use this to create our first simple set of types.
This seems to have addressed our problem! We now have a strong type for Position, Velocity, Acceleration and TimePoint. They cannot be swapped with each other, since they are fundamentally different structs.
While this solution works, it is clearly pretty cumbersome. We’ve defined 4 structs, each with an identical body. If we wanted to change anything in our implementation, such as requiring the value is provided upon construction, we’d need to update this in multiple places, which is obviously not ideal. When we see code duplication like this, the first thing to consider should be how we can reduce this, and in C++ that is often achieved by reaching for templates.
In order to solve this problem with templates, we need to consider how types are created from templates. When we have a templated class, every instantiation of that template with unique template parameters creates a new type, even if that template type isn’t used anywhere within the class! For example in the following, each of FooInt, FooFloat and FooBar are a unique type.
This example, particularly the last FooBar class should hopefully show the direction we are heading. We can use this information to define our “strong type” class one time, and have each instantiation use a different “tag” to differentiate it as a new type.
Now that we have this basic strong type defined, we can create new strong types with simple tag structs and some using declarations
We now have a strong typing system available where we can put any float into its own type with a unique tag, creating a new type that cannot be swapped with raw floats or any other strong type besides its own. From here it’s also easy to generalize by adding an additional template parameter T to our strong_type, allowing us to create strong types based on ints, floats, doubles, strings, vectors or really any type that we want.
At this point, we now have a fully usable set of strong types that we can use in our above function. Pulling all of this together, we now have the following piece of code.
We use the strong type class to create a unique type based on the tag for each of our parameter types. Note as well that the tags can be hidden away in a detail namespace, they shouldn’t really be accessed once they’ve been used in the type alias.
Our public API is now much more difficult to misuse, since swapping any parameters now causes a compile time error. Within the function body, we use the .value() function to decay down to the underlying type in order to perform the actual calculation. We’ve now succeeded in making a safer and more descriptive API. Indeed the API has become so descriptive from the types used that in the declaration we can potentially drop all the names besides the timepoints since the types themselves describe what the values are, which may simplify the API even more to a reader.
Of course, while this has improved the ergonomics for the API user, the question remains how this has affected the actual code that is run. If we’ve slowed down our calculation with the introduction of strong types, that’s clearly an undesirable outcome. To inspect that, we can look at the same function both with strong types, and with the primitive types.
Putting this example through compiler explorer, we can take a look at the generated assembly for each function. When compiling with -O1 level optimizations in clang, our generated assembly for the float implementation is
In comparison, the assembly for the strong type implementation is
In other words, both functions have an identical implementation. This shows that using strong types in this case is indeed a zero overhead at runtime, at any reasonable level of optimization.
We now have a fully functional, zero overhead strong type system to generate arbitrary types from arbitrary underlying types. As we saw in the above implementation, we were required to extract the underlying type for some of these operations. In a future post, we’ll go over how we can use the Curiously Recurring Template Pattern (CRTP) to add traits to the strong types, permitting us to add functionality without resorting to pulling out the weak type. Until then, the full strong type implementation is available on my GitHub at https://github.com/Infirmarian/strong-types for exploration, including the type trait implementation.
If you got this far, thank you for reading my first post! This is my first post on Hot Path, so if you have comments, feedback, questions or suggestions for future topics, please drop them in the chat below. And if you found this useful, consider subscribing for future posts!
No posts

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.