Last week, we discussed why we should sometimes use remove_cvref_t
on our template parameters before applying concepts to them. We also saw that the solution is not super readable because we lose access to the terse, shorthand syntax.
If only we could use concepts as template parameters!
Fortunately, P2841R7 comes to the rescue—and it has been recently accepted as part of C++26.
At first glance, the proposal may look daunting — it’s nearly 40 pages long. But don’t worry. First, it’s a very readable document filled with clear and detailed explanations. Second, about half of it consists of wording changes.
Let’s dive in.
Concepts as template template parameters
C++ already allows passing templates as template parameters, but only if they are class templates. A common reason for doing this is to allow higher-level abstractions. For instance, you may want to pass in a container template like std::vector
, without specifying the type it contains.
Jason Turner explains this well in C++ Weekly - Ep 368 - The Power of template-template Parameters: A Basic Guide, but here’s his example for quick reference:
1
2
3
4
5
6
7
8
9
10
11
template<template <typename Contained, typename Alloc = std::allocator<Contained>>
typename ResultType>
auto get_data() {
ResultType<double> result;
// ...
return result;
}
int main() {
auto data = get_data<std::vector>();
}
With a simpler signature like template<typename ResultType> auto get_data()
, we couldn’t pass in std::vector
, because it’s not a complete type — it’s a template. Thanks to template-template parameters, we can pass a class template to another template.
Unfortunately, until now this technique has not worked with variable templates or concepts. Yet the motivation for passing a concept as a template argument is similar to why we pass class templates: to enable high-level, expressive constructs.
Last week, we had this function template:
1
2
3
template<typename Q>
requires Quantity<std::remove_cvref_t<Q>>
void foo(Q&& q);
With concept template-template parameters, we can introduce a helper concept — in other words a concept adaptor — to improve readability:
1
2
3
template <typename T,
<template <typename> concept C>
concept decays_to = C<std::decay_t<T>>;
This allows us to rewrite our function more clearly:
1
2
template <decays_to<Quantity> Q>
void foo(Q&& q);
The proposal includes several other examples that would come in handy.
Variable template-template parameters
As of C++23, we cannot have variable template-template parameters. Although workarounds exist — such as wrapping variables inside structs with a value
member to use them as type template-template parameters — they are verbose, hard to read, and may negatively impact performance.
In fact, most standard type traits are defined as both types and _v
variables. According to the proposal’s performance benchmarks, using variable templates can have significant benefits for compile-time performance and memory usage.
The difference in the code example that paper brings is small:
1
2
3
4
5
6
// Before
template <template <typename> typename p, typename... Ts>
constexpr std::size_t count_if_v = (... + p<Ts>::value);
// After
template <template <typename> auto p, typename... Ts>
constexpr std::size_t count_if_v = (... + p<Ts>);
By eliminating ::value
, we avoid the overhead of instantiating a class template for each Ts
— a seemingly small change with meaningful impact.
The syntactic details
The reason concept and variable template-template parameters appear together in this proposal is not coincidental. While they were previously proposed separately, they are both essential for supporting Universal Template Parameters, proposed in P1985R3.
The proposal discussed here (P2841R7) is actually a subset of P1985R3. For a “universal template parameter” to live up to its name, it must support a broad set of possible template argument forms — including types, class templates, concepts, and variable templates.
Here’s how we used to specify a class-only template-template parameter:
1
2
3
template<
template <typename T> typename TT
>
With the new proposal, we can now include concepts and variable templates using the concept and auto keywords respectively:
1
2
3
4
5
template<
template <typename T> typename TT,
template <typename T> concept C,
template <typename T> auto VT
>
There are many more interesting aspects — like subsumption — but I’ll leave those for another article to keep this one focused and digestible.
Conclusion
The acceptance of P2841R7 into C++26 is one of those quiet but powerful improvements. It lets us write cleaner, more flexible, and easier-to-read templates by allowing both concepts and variable templates as template-template parameters.
This might seem like a niche feature, but if you’ve ever wrestled with awkward template syntax or boilerplate wrappers just to pass around concepts or traits, you’ll appreciate what this unlocks. It brings us one step closer to writing the kind of expressive, high-level C++ that feels more natural—without sacrificing performance.
It’s a great step forward for anyone who enjoys modern C++ and wants more elegant tools for metaprogramming. And let’s be honest — less ::value
clutter never hurt anyone.
Connect deeper
If you liked this article, please
- hit on the like button,
- subscribe to my newsletter
- and let’s connect on Twitter!
