This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 116c. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2025-03-14


1430. Pack expansion into fixed alias template parameter list

Section: 13.7.8  [temp.alias]     Status: open     Submitter: Jason Merrill     Date: 2011-12-13

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.

CWG 2022-11-11

There is no more implementation divergence; all known implementations reject the example.

Additional notes (January, 2025)

Using pack-index-specifiers allows to express some such substitutions, but not others. In the below example, we cannot express X. And while we can express Y, we cannot deduce through the replacement.

  template<typename A, typename...T> using X = std::tuple<T...>;
  template<typename A, typename...T> using Y = A;

  template<typename ...U> void f(X<U...> x, Y<U...> y);

Additional notes (March, 2025)

There is still implementation divergence for the following example:

   template <template <typename...> class T> struct Template {
   template <typename... Us> using type = T<Us...>;
   };

   template <class T> struct X { static constexpr T value = T(); };
   template <class T> using alias = X<T>;

   int main() { return Template<alias>::type<int>::value; }

GCC and EDG accept, clang rejects.

Also consider:

  template <template <int...> class T> struct Template {
    template <int... Us> using type = T<Us...>; 
  };
  template <T1 N> struct X { static constexpr int value = 0; };
  template <T2 N> using alias = X<N>;

If T1 = T2 = int, GCC and EDG accept and MSVC and Clang reject. If T1 = int and T2 = short, GCC accepts and everyone else rejects. If T1 = short and T2 = int, EDG accepts and everyone else rejects. See also issues 1979 and 1286; the alias template above would be considered "simple" only if T1 = T2.