C++20 - Filling blanks
What do you know about default parameters in C++? Well, C++20 introduced new elements that can be seen like default parameters.
Already known: Default parameters of functions
That in C++ functions can have default parameters is probably no big news.
In the example above, the function
Fun takes three parameters. One of them
z is defaulted to
0. This allows us to call
Fun with either two or three parameters:
In the case of A, the compiler injects the
0 such that the call effectively looks like
Fun(2, 3, 0).
Already known: Default arguments of template parameters
Another instance of default parameters are defaulted template arguments:
Fun is a function template with two template type parameters,
U. The usual way to invoke this functions is:
However, since there is a default argument present for
U, we can use that:
The call to
Fun results in the same call as before when we explicitly specified
int. Feel free to use C++ Insights to verify this.
New elements of C++20
All right, we look at the past now, let's see the additions of C++20. We are looking at three new places which I will walk you through:
- Constraint placeholder types
- Abbreviated function templates with a template-head and constrained placeholder types
- Compound requirement
In all these cases, we can have a scenario where an argument can be defaulted.
Constraint placeholder types
In C++20, we have Concepts that allow us to constrain placeholder types. The
auto in an abbreviated function template is such a placeholder type.
Abbreviated function templates are a new element of C++20. They allow us to use
auto as a function parameter:
The definition of
Fun is essentially a function template. The compiler does the transformation for us, leaving us with a nice short syntax. You may already know this from C++14's generic lambdas.
For the following, assume that we have two classes,
B derives from
A. Further, we like to have a function template
Fun which takes a single
auto parameter. This parameter is constrained with
std::derived_from to ensure that
Fun is only called with types that have
A as a base class. Because
Fun takes the parameter by value, we cannot use the base class. This could result in slicing. Our code then looks like this:
1 2 3 4 5 6 7 8 9 10
The part where default parameters come into play is the constraint
std::derived_from for the placeholder type. Looking closely at the code, you can see that
derived_from is called only with one parameter,
A. Yet the definition of
derived_from requires two parameters. How else could
derived_from do its check? However, the code as presented works fine. The reason for that is that the compiler has the power to inject parameters into concepts. Internally the compiler injects
B, the type
auto deduces, as the first argument to
Aside from the fact that this is very neat, we are looking at something new. This is the first time default parameters, or better omitted parameters, get inserted from the left. In the previous cases, the compiler starts filling from the right.
Abbreviated function templates with a template-head and constrained placeholder types
One variation of the above is once we mix abbreviated function templates with a template-head:
1 2 3 4 5 6
In this specific case, the compiler appends a template parameter to the template-head for our
std::derived_from is still filled from the left.
Fun in a namespace to see how it is treated internally with C++ Insights.
One interesting thing we can do with that is having a variadic template parameter followed by another template parameter:
1 2 3 4 5 6
We cannot have this without
auto-parameters. However, this is the only form I know of that works. As soon as you try using the parameter pack as function arguments, it stops working. The compiler doesn't know when the pack is terminated.
A compound requirement
With Concepts, we got a requires expression that can host a compound requirement. The purpose of a compound requirement is to check:
- If a function is
- Whether the return type of a function satisfies a concept.
We can check only one of them or both. For the following example, only the second check is used:
1 2 3 4 5 6 7 8 9 10 11
With this piece of code, we ensure with the help of the concept
Silly, that the member function
Fun of a class
T returns a type that is derived from
A. In the
derived_from check, we see the same pattern we previously saw in constraint placeholder types. The compiler injects the missing argument, once again from the left. This is important because the check would not work if the compiler filled the right value.
In a nutshell
The table provides an overview of the various elements in C++ where the compiler fills in the blanks for use when it comes to parameters.
|Type||From right||From left|
|Default parameters of functions||X|
|Default arguments of template parameters||X|
|Constrained placeholder types||X|
|Abbreviated function templates with a template-head||X|
Diving into C++20
In case you like to learn more about C++20's Concepts, consider my book Programming with C++20.
In 2021 I gave various talks about Concepts. Here is one recording from CppCon: C++20 Templates: The next level: Concepts and more.