Reputation: 65770
This code produces a compiler error in GCC, Clang, and MSVC (this is a heavily reduced test case for some real code that actually does something useful):
#include <cstdint>
#include <concepts>
template <std::size_t N>
struct a {
template <class T0, class...Ts>
using f = T0;
};
template <std::size_t N>
struct b {
template <class... Ts>
using f = typename a<N>::template f<Ts...>;
};
static_assert(std::same_as<b<0>::f<int,char,bool>, int>);
int main() {
}
Error:
<source>:13:41: error: pack expansion used as argument for non-pack parameter of alias template
13 | using f = typename a<N>::template f<Ts...>;
| ^~~~~
<source>:12:5: note: in instantiation of template type alias 'f' requested here
12 | template <class... Ts>
| ^
<source>:16:28: note: in instantiation of template class 'b<0>' requested here
16 | static_assert(std::same_as<b<0>::f<int,char,bool>, int>);
| ^
<source>:6:17: note: template parameter is declared here
6 | template <class T0, class...Ts>
| ^
Godbolt link: https://godbolt.org/z/Thrvon7T4
If I change the instantiation of a
to be dependent on Ts...
, then it compiles cleanly:
template <std::size_t N>
struct b {
template <class... Ts>
using f = typename a<((void)sizeof...(Ts),N)>::template f<Ts...>;
};
Godbolt link: https://godbolt.org/z/sao7fzzYc
What are the relevant rules that govern this difference? In other words, why is the version where the specialization of a
is dependent on Ts
well-formed, but the version where it is not is ill-formed?
Here is a simpler way to reproduce:
template <typename T, typename... P>
using type1 = T;
template <typename... P>
using type2 = type1<P...>;
error: pack expansion argument for non-pack parameter 'T' of alias template 'template<class T, class ... P> using type1 = T'
using type2 = type1<P...>;
NB AS shown in compiler explorer, the issue seems to be the first template being an aliased type, as with a plain class there's no such issue.
Upvotes: 7
Views: 132
Reputation: 2776
This is only a partial answer. Noticeably, it does not address why using f = typename a<((void)sizeof...(Ts),N)>::template f<Ts...>;
is working.
What can be observed is a difference in handling of alias template and other kind of templates:
#include <type_traits>
template <typename T, typename... P>
using type_alias1 = T;
// refuses to dispatch P on T and the parameter pack
// template <typename... P>
// using type_alias2 = type_alias1 <P...>;
template <typename T, typename... P>
struct wrapped_type_alias {
using type = T;
};
// accepts to dispatch P on T and the parameter pack
template <typename... P>
using type_alias3 = wrapped_type_alias<P...>::type;
int main() {
// static_assert(std::is_same_v<type_alias2<int, bool, char>, int>);
static_assert(std::is_same_v<type_alias3<int, bool, char>, int>);
}
When trying to pass a parameter pack expansion to an alias template; OP's error occurs. Passing it to another kind of template works as expected (the first element of the pack is associated to the non-pack parameter).
This behavior does not depend of where the pack expansion occurs (declaring a (member) variable of type type_alias1<P...>
fails also).
Unfortunately, I'm enable to find a corresponding rule within the standard, though the 3 tested compilers (gcc, clang, cl) exhibit the same behavior.
A bit of research seems to indicate that this a problem known from a long time:
Bug report have been filled but it seems that it still an open discussion if it's a bug or not:
As a sidenote, workarounds exist:
template <typename T, typename... P>
using type_alias1 = T;
template <typename T, typename... P>
using type_alias2 = type_alias1<T,P...>;
The template using the pack expansion mimics the original template parameters list.
namespace impl
{
template <class T0, class... TS>
struct first {
using type = T0;
};
}
template <typename... P>
using type_alias1 = impl::first<P...>::type;
template <typename... P>
using type_alias2 = type_alias1<P...>;
The original template alias is modified to accept only a template parameter pack that is forwarded to a class template, for which the expansion issue does not occur.
Upvotes: 2