Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variadic template aliases as template arguments

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.

like image 726
iavr Avatar asked Nov 27 '13 21:11

iavr


1 Answers

ISO C++11 14.3.3/1:

A template-argument for a template template-parameter shall be the name of a class template or an alias template, expressed as id-expression.

Plus I don't see any special exceptions for variadic template template parameters.

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).

Root of problems can be in other places. You should try to localize code which causes internal compiler error - just remove unrelated parts one by one (or use some kind of binary search, i.e. divide-and-conquer) - and check if error is still here on each stage.


As for GCC 4.9.0 error, try to change

template <typename... T>
using map = F <T...>;

to

template <typename... U>
using map = F <U...>;

Maybe this would help to understand what GCC sees.

like image 119
Evgeny Panasyuk Avatar answered Sep 23 '22 16:09

Evgeny Panasyuk