Pack expansion for alias template
Asked Answered
S

2

50

It seems that a pack argument can be expanded only in the place of a pack parameter of an alias template. This is not true for a class or a function template:

template <class T, class... Args> struct x { using type = T; };

template <class T, class... Args> using x_t     = typename x<T, Args...>::type;
template <class... Args>          using x_fix_t = typename x<Args...>::type;

template <class... Args> auto f(Args...) -> void {
  typename x<Args...>::type v1; // OK
  x_t<Args...> v2; // Error
  x_fix_t<Args...> v3; // OK
}

simpler case:

template <class T, class U> using y_t = T;

template <class... Args> auto f(Args...) -> void {
  y_t<Args...> v4; // Error
}

The above code generates error (even if f is never instantiated) with both c++11 and c++14 in g++ 4.9, g++ 5.1 and clang 3.5.

Why is this not permitted and what is the general rule? I see no reason to restrict this. It seems a very strange prohibition.

As for why not writing as x_fix_t with the first variant it is more clear that x_t has a mandatory first argument. (for instance that is the reason f() is not permitted). But this is not that important, the fix is easy. The question remains: Why?

gcc error:

error: pack expansion argument for non-pack parameter ‘T’ of
alias template ‘template<class T, class ... Args> using x_t = typename x::type’

clang error:

error: pack expansion used as argument for non-pack parameter of
alias template   x_t<Args...> v2;
Stripper answered 8/6, 2015 at 10:40 Comment(4)
The info here is relevant, I think: https://mcmap.net/q/356004/-unpacking-parameter-packs-in-template-aliases/4326278Evy
Also, for what it's worth, MSVC 12 and 14 RC compile this with no diagnostics (aside from normal warnings for unreferenced variables) - "implementation variance", as they say.Evy
Works OK in g++ 4.8.2.Caveator
ICC 13 accepts the code as well -- is there any Standardese someone can point at that says no? Otherwise, this is a very good find -- a regression in not one, but two disparate implementations!Covering
D
31

This compiles in GCC 4.8 but fails in GCC 4.9, which is evidence that it's related to CWG 1430 and bug report #59498. The fix proposed by Roy Chrihfield is the exact same one as yours:

Rewriting the code to use a struct succeeds:

template <typename T, typename ...>
struct alias { using type = T; };

template <typename ...T>
using variadic_alias = typename alias<T...>::type;

Furthermore, Jason Merrill elaborates on why it should fail:

Actually, no, this is very much a Core 1430 issue; there's no way to mangle variadic_alias<T...> without mentioning the name of an alias template in the mangling, and they're supposed to be entirely transparent. This only works in 4.8 by accident because checking is disabled for the release.

No further discussion exists in the bug report, so we can turn to CWG 1430:

Originally, a pack expansion could not expand into a fixed-length template parameter list, but this was changed in N2555. This works fine for most templates, but causes issues with alias templates.

In most cases, an alias template is transparent; when it's used in a template we can just substitute in the dependent template arguments. But this doesn't work if the template-id uses a pack expansion for non-variadic parameters. For example:

template<class T, class U, class V>
struct S {};

template<class T, class V>
using A = S<T, int, V>;

template<class... Ts>
void foo(A<Ts...>);

There is no way to express A<Ts...> in terms of S, so we need to hold onto the A until we have the Ts to substitute in, and therefore it needs to be handled in mangling.

Currently, EDG and Clang reject this testcase, complaining about too few template arguments for A. G++ did as well, but I thought that was a bug. However, on the ABI list John Spicer argued that it should be rejected.

(See also issue 1558.)

Notes from the October, 2012 meeting:

The consensus of CWG was that this usage should be prohibited, disallowing use of an alias template when a dependent argument can't simply be substituted directly into the type-id.

Additional note, April, 2013:

For another example, consider:

  template<class... x> class list{};
  template<class a, class... b> using tail=list<b...>;
  template <class...T> void f(tail<T...>);

  int main() {
    f<int,int>({});
  }

There is implementation variance in the handling of this example.

In otherwords, this is an on-going issue without any resolution (AFAIC) in sight.

Dempsey answered 14/10, 2015 at 16:40 Comment(0)
R
-1

It is an annoying inconvenience, but there is a workaround:

make the using template take a single typename T, and make the caller wrap the variadic arguments in a types_t container

template<typename... Ts> 
struct types_t{};

and then, from the using now accepting a types_t, delegate the work of unwrapping and using the variadic arguments in another metafunction

Radiocarbon answered 3/7, 2023 at 12:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.