C++26 Reflection gives us universal template parameters

ibobev1 pts0 comments

C++26 Reflection gives us universal template parameters – Arthur O'Dwyer – Stuff mostly about C++

C++26 Reflection gives us universal template parameters

Keenan Horrigan on the std-proposals mailing list pointed out<br>an interesting consequence of C++26 Reflection: it seems to give us “universal template parameters”<br>almost for free.

The STL sometimes tries to pretend we have “universal template parameters” by overloading templates<br>with the same name. For example, we have both std::get(tp) taking a type and std::get(tp)<br>taking an integer; but these are just two completely different signatures of std::get in the same overload<br>set. Likewise in C++23 you can write either rg | ranges::to>() (where the argument to<br>to is a type) or rg | ranges::to() (where the argument is a class template).<br>Again this is accomplished with an overload set — roughly like this (modulo I’ve totally mangled<br>the actual job of ranges::to, which is generally not to forward its arguments to To’s constructor):

template // #1<br>auto rangish_to(Args&&... args) {<br>return To(std::forward(args)...);<br>template class To, class... Args> // #2<br>auto rangish_to(Args&&... args) {<br>return To(std::forward(args)...);

int *a, *b;<br>auto x = rangish_to>(a, b); // OK, #1<br>auto y = rangish_to(a, b); // OK, #2

But even this overload set won’t accept a caller like:

auto z = rangish_to(a, b); // error

because std::ranges::subrange as a template argument matches neither class To nor<br>template class To. It’s actually a class template of two type parameters and a constant!<br>(cppreference.)

enum class subrange_kind : bool {<br>unsized,<br>sized,<br>};<br>template S = I,<br>subrange_kind K = sized_sentinel_for ? sized : unsized><br>class subrange { ~~~~ };

We could accept subrange ad-hoc via a third overload:

template class To, class... Args> // #3<br>auto rangish_to(Args&&... args) {<br>return To(std::forward(args)...);

but in general there’s no way to write out the set of all possible kinds of<br>class templates, so we can’t make our rangish_to accept all of them.

You might point out that the real ranges::to wouldn’t accept subrange anyway, because the real<br>ranges::to accepts only containers like vector, not views like subrange or span. However,<br>this deficiency is likely to be DR’ed<br>in the near future thanks to Hewill Kang’s well-received<br>P3544 “ranges::to.”

C++26 Reflection to the rescue! C++26 Reflection is “untyped”: it uses<br>a single type<br>std::meta::info to represent the reflections of every kind of thing<br>in the language. Therefore a template parameter of type meta::info alone can represent<br>std::vector, std::ranges::subrange, std::array, or anything else (std::vector,<br>size_t, 42, std::ignore…) and so we can write a single function template like this<br>(Godbolt):

template<br>auto meta_to(Args&&... args) {<br>return typename [:To:](std::forward(args)...);

auto x = meta_to>(a, b); // OK<br>auto y = meta_to(a, b); // OK<br>auto z = meta_to(a, b); // OK!

For the cost of two characters ^^ (and a really cryptic error message if you forget<br>the typename keyword), C++26 seems to have given us universal template parameters!

(Does this mean P2989 “Universal template parameters”<br>is obsolete? IMHO, yes. But I can see how one might debate it: those two ^^ characters<br>are kind of ugly.)

Posted 2026-06-07

class-template-argument-deduction

paradigm-shift

ranges

reflection

templates

template args class auto ranges parameters

Related Articles