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.
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.
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;
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?
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.