The difference between static_assert and C++20's requires
Over this year, I gave various presentations and classes about C++20's Concepts. In today's post, I like to discuss the difference between a static_assert
and a Concept, or better, requires
.
I previously wrote about C++20's Concepts. For reference, these are my previous posts about C++20 Concepts:
- C++20 Concepts: Subsumption rules
- C++20 Concepts: Testing constrained functions
- How C++20 Concepts can simplify your code
This post is motivated by my talk "C++20 Templates: The next level - Concepts and more".
There I start with the task of writing a function Add
which adds an arbitrary amount of values together. One restriction of Add
is that the function should work only with values of the same type.
A possible C++17 solution
My solution in C++17 is the following:
1 2 3 4 5 6 |
|
This solution is based on two helpers, are_same_v
, which checks whether all types in a parameter pack are of the same type. The second helper is first_arg_t
which essentially grabs the first parameter of a parameter pack. Since all types are the same, this is what are_same_v
checks, the first type is equal to all others. Below you find the helpers for completeness.
1 2 3 4 5 6 7 8 9 10 11 |
|
A possible C++20 solution
Now, using C++20, my solution is the following:
1 2 3 4 5 6 7 |
|
As you can see, I only need the are_same_v
helper.
A solution using static_assert
All right, this is just to get you on the same page. I'm aware that there are several other possible solutions out there. What I dislike about the C++17 approach is the enable_if_t
- way too complicated. For the full picture, feel free to watch my talk. Today I like to focus on an alternative C++17 implementation without enable_if
:
1 2 3 4 5 6 |
|
That solution looks a little less scary. Much like the C++20 version, it only requires are_same_v
as a helper. I could also use the optional message of the static_assert
to generate a, hopefully, meaningful message for users.
Comparing static_assert
to requires
While this C++17 solution looks good, there is a huge difference between it and the C++20 approach: static_assert
is hidden inside Add
. We are looking at a very small example here, only two lines of code in the body, something you most likely do not have that often in your real-world code. The deeper the static_assert
is hidden, the worse it is. This static_assert
models a requirement for Add
. As a user, I want to know such a requirement upfront. Regardless of how nice you formulated the optional message, I will not be thrilled if that static_assert
fires. This approach also makes it impossible to provide an overload to Add
, which would treat different types.
The C++20 solution states the requirement clearly in its function signature as my initial C++17 version does. While sometimes the initial C++17 version looks too scary and may feel too complicated to write, C++20 gives us an easy way to express our intent. Beyond that, C++20 allows us to express the difference between a requirement and an assertion.
Express the difference between a requirement and an assertion
In C++20, use concepts or requires
-clause as early as possible. Replace class
/ typename
with a concept if possible. Use a requires
-clause as a fallback. That way the requirements are stated clearly for users without a need to read the function body and spot limitations there.
Use static_assert
for assertions that should not occur on a usual basis. Something that may depend on the system the program is compiled for or similar things that are less related to the template type.
Andreas