Variadic template aliases as template arguments
Asked Answered
L

1

17

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.

Laszlo answered 27/11, 2013 at 21:32 Comment(4)
The error message of the experimental GCC 4.9 does not make sense to me, and FWIW I think the code is valid.Kalliekallista
Related? https://mcmap.net/q/746942/-variadic-template-deduction-in-variadic-template-template/420683Discovery
Thanks, this question is related in that templates dealing with special cases like 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
FWIW, both clang++ 3.6.0 and g++ 5.1.0 report an error.Rave
I
3

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.

Isle answered 29/11, 2013 at 0:24 Comment(1)
Thanks. I have not been able to isolate the code that is in error in the past, because removing even small parts made the errors disappear. With gcc 4.9 this was the first time I could see a clear error message, and I was "happy" because if true, it could explain almost everything (even though every solution will be ugly). Anyhow, since the problems are still there, I will try to make a new example with unexpected result in other compilers and come back with a new question.Laszlo

© 2022 - 2024 — McMap. All rights reserved.