Search code examples
c++templatestypedeftype-deduction

Type aliases and template template argument deduction


Problem:

I have noticed that there is a bit of an inconsistency during template template deduction when using type aliases. In particular a type alias can be used as template template argument but is not deduced as one.

Example:

We will use Matched<Type> to see if Type is an instance of a templated type.

template <typename T>
bool Matched = false;

template <template <typename> typename F, typename T>
bool Matched<F<T>> = true;

Now define a type alias

template<typename T>
using Alias = std::tuple<T,T>;

And my problem is that Matched<Alias<int>>==false. However Alias can be used as a template template argument, for example:

template<template<typename> typename F>
using ApplyInt = F<int>;

Then ApplyInt<Alias> works fine.

To recap, in ApplyInt<Alias> is treated as a template template argument but not in Matched<Alias<int>>. I find this a little stupid because I think about type aliases as functions on types and I would like to work with them. Right now, type aliases are treated as second class citizens compared to types and this makes hard to work with them in a generic way, such as composing or transforming them.

Possible ways out:

1.Change deduction rules such that type alias is detected as template template argument. This would make Matched<Alias>==true.

2.Allow usage of using in template declaration like this:

template<template<typename> using T, typename T>
bool Matched<F<T>> = true;

Question:

Is this behavior intentional? Is this an oversight? Was this noticed and will it be fixed in a future version in c++?


As a side note: The similar problem is with variable templates. Why cannot we write?

template <template<typename> auto Var>
auto VarForInt = Var<int>;

Edit(after accepting the answer):

I'm really puzzled by the type deduction. When we store a type alias in a helper class

template<template<typename> typename F>
struct Helper{};

and we have a function

template<template<typename> typename F>
void foo(Helper<F>){}

We can call foo(Helper<Alias>{}). Isn't Alias "deduced" in the function call? Or this is not called type deduction?


Solution

  • Yes, that is intentional. Alias templates are indeed, as you said, somewhat of a "second class citizen". To start with, alias templates cannot be specialized, that's a real big hint right there.

    Now, their "lower grade" as evident in your example is all about [temp.alias]/2:

    When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template. [ Note: An alias template name is never deduced. — end note ]

    The above means, that when you write Matched<Alias<int>>, since Alias<int> refers to a specialization of the alias template, it's equivalent to directly writing Matched<std::tuple<int,int>>. And it's quite obvious why that doesn't match the specialized variable template.

    It's not an oversight, nor is it going to be fixed. Alias templates are there to provide a shorthand for more complex template expressions. And you wouldn't want the wrong overload to be called, or the wrong template specialization instantiated, because you used a shorthand instead of the whole complex expression.