Introduction to C++20 Concepts
Why we need Concepts
?
If we declare multiple classes:
1 |
|
I ever met a situation where I would like to deserialize a string expression into an object which must be inherited from Base
.
The original declaration of the parser:
1 |
|
But how can we create an explicit constraint that T
inherits from Base
?
Of course, it’s sufficient to use native pointers.
Let’s assume we have to use smart pointers. :)
Constraints are neccessary actually
For readability and debugging, it is necessary to express constaints explicitly.
Here is a kind of implmentation in C++11:
1 |
|
But it’s invasive! We had modified the appearance of the return type int
.
Rewrite it
We can rewrite it in another form:
1 |
|
As our common feeling, it’s still ugly. :(
Rewrite it again
For type traits:
After C++14 xxx_t<T>
is available and it’s equivalent to xxx<T>::type
(even typename xxx<T>::type
)
After C++17 xxx_v<T>
is available and it’s equivalent to xxx<T>::value
The previous code can be rewrited as below:
1 |
|
It’s still a little hard to read and understand, especially since the second argument looks strange in the template argument list. We clearly need a constraint but why we need to bring in a weird thing like typename = ...
?
Besides, template hell is horrible when displaying compiling error messages. It particularly affects the efficency to debug.
Concepts
is coming
At the end of this page, this example rewritten by concepts
will be shown.
The simplest concepts
1 |
|
It’s easy to understand that concepts are essentially compile-time constant booleans.
Unite concepts and constexpr bool
Here provides a way to reuse constexpr bool so that we may have impression that concepts can be united with constexpr bool.
1 |
|
Requirements on operations
Assume we had declared a concept named whose declaration like below:
1 |
|
The concept has at least these 3 ways to use:
1 |
|
1 |
|
1 |
|
Constraints on member functions
1 |
|
In this case, I would like to show two places to notice:
- The uncommented line is exactly equivalent to the commented out line although their forms have something different. The uncommented forms is a syntactic sugar. It means the return type of
t.power()
is filled in the first parameter position ofstd::same_as
, and the typeint
is actually the second parameter. - To decorate
auto
, or more precise saying is to constrain it, we can use a concept beforeauto
. It’s a new usage.
Constraints on member variables
1 |
|
Here we need to notice that t.power
is actually a lvalue so the use of std::same_as
should be done carefully.
Multiple typenames
I’ve introduced how to write a concept that indicates a single type is addable. How do we want to write a concept that indicates multiple types are addable?
It’s not really hard and we can quickly make it:
1 |
|
And we can also summarize its usage with 3 forms corresponding to the single type concept.
1 |
|
1 |
|
1 |
|
The second and the third look weird. They are similar to the syntactic sugar just mentioned. T
is the first parameter and Y
is the second. In order to declare T
(or auto t
), we have to swap they positions and declare Y
(or auto y
) in advance. It likes a trick and there may be some difficulty to understand. Therefore, we have to take some tradeoffs between readability and writability.
Constraints on return values
1 |
|
Best Pratice: Prefer concept names over auto
for local variables[1]
1 |
|
Association: static interface/polymorphism
1 |
|
It seems it implements a simple factory pattern by type enumeration values.
From another perspective, concept also looks like interface though it’s static context.
Most importantly, it’s readable compared with a single auto
.
Anonymous Concept
1 |
|
The requires
written twice isn’t a typo. It means it’s a anonymous concept used just here.
Merge multiple statements as far as possible
I summarize it as a best practice.
The Duplicative Form
1 |
|
The Concise Form
1 |
|
Rewrite the previous example by Concepts
It’s time to rewrite the previous example by Concepts
!
Let’s review the previous form:
1 |
|
And rewrite it to a new form:
1 |
|
As we see, the readability of the form rewritten by concepts
is undoubtedly better than the previous form. It directly points out the concept is a constraint by keyword requires
. It’s great.