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,
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
A solution using
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
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.
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
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.
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.