First some code, then some context, then the question:
template <typename T> using id = T;
template <template <typename...> class F, typename... T>
using apply1 = F <T...>;
template <template <typename...> class F>
struct apply2
{
template <typename... T>
using map = F <T...>;
};
// ...
cout << apply1 <id, int>() << endl;
cout << apply2 <id>::map <int>() << endl;
Both clang 3.3 and gcc 4.8.1 compile this without error, applying the identity metafunction to int
, so both expressions evaluate to a default int
(zero).
The fact that id
is a template <typename>
while apply1
, apply2
expect a template <typename...>
did concern me in the first place. However, it is quite convenient that this example works because otherwise metafunctions like apply1
, apply2
would have to be so much more involved.
On the other hand, such template aliases cause serious problems in real-world code that I cannot reproduce here: frequent internal compiler errors for gcc, and less frequent unexpected behavior for clang (only in more advanced SFINAE tests).
After months of trial and error, I now install and try the code on the (experimental) gcc 4.9.0, and here comes the error:
test.cpp: In instantiation of ‘struct apply2<id>’:
test.cpp:17:22: error: pack expansion argument for non-pack parameter ‘T’ of alias template ‘template<class T> using id = T’
using map = F <T...>;
^
Ok, so it seems this code was not valid all this time, but gcc crashed in various ways instead of reporting the error. Interestingly, while apply1
, apply2
appear to be equivalent, the error is only reported for apply2
(which is much more useful in practice). As for clang, I really cannot say.
In practice, it seems I have no other way than to go along with gcc 4.9.0 and correct the code, even though it will become much more complex.
In theory, I would like to know what the standard says: is this code valid? If not, is the use of apply1
invalid as well? or only apply2
?
EDIT
Just to clarify that all problems I've had so far refer to template aliases, not template structs. For instance, consider the following modification:
template <typename T> struct id1 { using type = T; };
// ...
cout << typename apply1 <id1, int>::type() << endl;
cout << typename apply2 <id1>::map <int>::type() << endl;
This compiles fine and prints 0
in both cases, on clang 3.3, gcc 4.8.1, gcc 4.9.0.
In most cases, my workarounds have been introducing an intermediate template struct before the alias. However, I am now trying to use metafunctions to parametrize generic SFINAE tests and in this case I have to use aliases directly, because structs should not be instantiated. Just to get an idea, a piece of the actual code is here.
foo
,foo2
,foo_variadic
etc. are exactly the way I was planning to correct the code if I have to. However, as I have edited above, my problems only appear with template aliases. – Laszlo