C++ Logo

std-proposals

Advanced search

Re: [std-proposals] consteval typename auto - type-returning immediate functions

From: Charles R Hogg <charles.r.hogg_at_[hidden]>
Date: Sun, 26 Oct 2025 16:36:40 -0400
I am not sure about the syntax, but I have long yearned for something very
much like this. You phrase this as "consteval functions that return
types", but I think that's just one possible approach to fill this need
that I feel so keenly. Perhaps a more general phrasing would be
"expression-based aliases". What we want is some kind of alias syntax that
can take advantage of the natural composability and ergonomics of values in
expressions. Most especially, we want to be able to take advantage of
*template
argument deduction*.

Units libraries present a very concrete use case. I contribute to two: Au
<https://github.com/aurora-opensource/au> (where I am the primary author)
and mp-units <https://github.com/mpusz/mp-units> (where I am a contributing
author). Au supports C++14 and later, and mp-units has a C++20 minimum
requirement.

Both libraries have excellent composability of *objects*, where many
objects are callables that create or alter *quantities*. In Au, just as
you can use callable objects `meters` to write meters(100.0), and `seconds`
to write seconds(5.0), you can write (meters / second)(20.0), composing
the objects `meters` and `second` (`seconds` would also work) on the fly.
You can even compose prefixes and mathematical operations, such as
(kilo(meters)
/ squared(minute))(35.28). It's great! And mp-units has every bit of this
composability.

What mp-units has that Au *cannot* get is composability of *types*. This
really matters when you are adding members to a `struct` or `class`, or
declaring function parameters. In mp-units, one can write:

quantity<m / s, *double*> speed;

This is amazing! But in Au, which cannot rely on C++20 support, the best
we can do is:

QuantityD<UnitQuotientT<Meters, Seconds>> speed;

Rather clunky by comparison.

The enabling C++20 feature is Non-Type Template Parameters (NTTP). Instead
of templating the `quantity` type on a *type* for the "unit", we template
it on a *value*. This "value" is not a usual runtime value like `int` or
`double`; rather, it's an example of a "monovalue type
<https://aurora-opensource.github.io/au/0.5.0/reference/detail/monovalue_types/>"
(disclaimer: my term, not a standard term). The type that represents
meters per second can only ever hold one value, and that value is "meters
per second".

It's great that this gives us composability of typenames: it's a huge win.
But after using it for a while and pondering it, I feel like NTTPs are more
of a convenient facsimile, rather than what we really want. I think we
would rather simply have types! And I think the reason we don't is because
types are just so clunky to compute with, and they can easily pick up
"decorators" such as `const` or `&` which are often undesired.

What I think I really want is the ability to give inputs to a type alias *using
function call syntax*, specifically so that we can *deduce* some of the
types. For example, let's say the goal would be to write something like
this:

quantity_of<*double*>(m / s) speed;

This would be equivalent to `QuantityD<UnitQuotientT<Meters, Seconds>>`
above (where the `D` suffix was an existing shorthand for `double`).

We could imagine implementing this in several different ways. For example,
it might look a lot like a function declaration, but with some suitable
tweak to indicate that it's "returning" a type name:

*template* <*typename* T, *typename* U>
Quantity<AssociatedUnitT<U>, T> quantity_of(U) -> *typename*;

If we used this syntax, it wouldn't support definitions for the "function":
it means the "declaration" already has all of the information we need. Or,
we could do something that looks more like a traditional alias, but with a
way to provide parameter *values*, which are used *purely for type
deduction* purposes, perhaps like this:

*template* <*typename* T, *typename* U>
*using* quantity_of(U) = Quantity<AssociatedUnitT<U>, T>;

Obviously, both of these syntax ideas are just spitballing. As written,
they may not be desirable, or even feasible. The point is that:

   - working with *instances* in C++ is generally natural, flexible, and
   expressive
   - going from the "realm of types" to the "realm of instances" is easy,
   especially for monovalue types: just add `{}`
   - going from the "realm of instances" to the "realm of types" is at best
   clunky and un-expressive, and is often error prone
   - we could reduce this friction by adding some construct that takes
   *instance* parameters (and thus supports template type deduction), but
   whose "return" value is *already a type*

This may be a bit quixotic, in that a C++14-supporting library could never
take advantage of this without going *past* the C++20 minimum bar of
mp-units. But, it seems to me that providing a lower-friction pathway for
the "values-to-types" direction would improve expressiveness generally in
the library, and is thus worth pursuing.



On Sun, Oct 26, 2025 at 3:44 PM Muhammad via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> Allow consteval functions to return *types* by using the declarator typename
> auto. These functions would be evaluated at compile time and usable
> anywhere a type is expected.
>
> Motivation
>
> -
>
> Express compile-time type computation using normal control flow.
> -
>
> Zero runtime cost since consteval are deduced at compile time.
>
> Rules (proposal)
>
> -
>
> Must be declared consteval.
> -
>
> Each return must produce a type expression (e.g. int, std::vector<T>,
> decltype(expr)).
> -
>
> Usable in using, declarations, template arguments, concepts, etc.
>
> Why
>
> -
>
> More readable than alias templates/struct metafunctions for many cases.
> -
>
> Aligns type-level programming with existing constexpr/consteval
> function model
>
>
> Current syntax:
>
> template<int N>
> struct select_type {
> using type = std::conditional_t<N == 0, int, double>;
> };
>
> using T = typename select_type<1>::type; // T == double
>
> Proposed syntax:
>
> consteval typename auto select_type(int n) {
> return n == 0 ? int : double;
> }
>
> or using the trailing type syntax
>
> consteval auto select_type(int n) -> typename auto {
> return n == 0 ? int : double;
> }
>
> using T = select_type(1); // T == double
>
> More examples
>
> template<typename T>
> consteval typename auto make_pointer_if(bool b) {
> return b ? T* : T;
> }
>
> using A = make_pointer_if<int>(true); // int*
> using B = make_pointer_if<int>(false); // int
>
> tell me what you think.
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2025-10-26 20:36:54