C++ quick tips: Concepts, type constraints and c++20 coding style
c++20 introduced concepts to the standard thanks to which now, we can specify constraints and restrictions on template parameters that a given type, variable or a function template accepts. Similarly as with e.g. virtual classes defining interfaces for a family of types through inheritance, concepts allow creating interfaces for generic code. With concepts, just by looking at the template declaration we know what to expect and what types are accepted. Having a simple template like:
|
|
It’s obvious that IntCalc
can only work with types adhering to std::integral
concept.
I can define a concept for IntCalc
specifically.
|
|
With the above at hand, I can declare a CalcUser
class template that only
accepts types adhering to the IntCalcLike
concept:
|
|
What about auto
?
Type constraints can be specified on auto
declarations as well. Consider the following:
|
|
This statement expresses a declaration where x
’s deduced type must be
conformant with the std::integral
template.
The deduced type constraint must directly precede the auto
keyword. There’s
a quirky consequence to this:
|
|
const
in the beginning of the declaration seems too “detached” for lack of a
better word, which makes me think that “east-coast” const
style is the way to
go :).
template <std::integral auto X> what?
With concepts, auto
sneaks in to template arguments syntax as well:
|
|
At first glance, the difference between template <concept T>
and template <concept auto V>
might not be obvious. I wasn’t sure myself what to think of it, the first time
I’ve seen it. The meaning between two expressions is completely different though:
template <concept T>
- type constrained by a concepttemplate <concept auto V>
- value who’s deduced type is constrained by concept
This syntax can be used in function templates as well:
|
|
Constraints can be applied to return values as well:
|
|
Another context where type deduction happens quite often are range for loops:
|
|
Concept based overloading
Given a set of type constraints defined by concepts, it’s possible to have a concept based function overloading.
|
|
Types compliant with different concepts will be dispatched to different overloads.
decltype(auto)
Just as a reminder, decltype
gives back the type of a variable or an
expression given as a parameter. decltype(auto)
is used to preserve exact
type and category of an expression. It is used mostly for perfect forwarding
of return values, as during template type deduction references, under variety
of conditions, might be dropped.
Let’s start with some basic concepts:
|
|
I can use getIndex
the following way:
|
|
This does not involve perfect forwarding yet. I’d need perfect forwarding to preserve the actual type of the return value from the container - which in case of vector is gonna be the reference to the value type (most of the time):
|
|
With c++20, it’s possible to constraint decltype(auto)
as well:
|
|
Thanks to the above, if we’re working with a container who’s operator[]
doesn’t return a reference (like std::vector<bool>
), then the compiler will
error out straight away:
|
|
Conclusion
In my opinion, concepts are the best addition, greatly improving working with generic code. Concepts allow for templates to be more expressive, safe and form a true set of interfaces between objects involved.