别名模板的包扩展

Pack expansion for alias template

本文关键字:扩展 包扩展 别名      更新时间:2023-10-16

似乎只能在别名模板的pack形参的位置展开。对于类模板或函数模板则不是这样:

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
}

简单的例子:

template <class T, class U> using y_t = T;
template <class... Args> auto f(Args...) -> void {
  y_t<Args...> v4; // Error
}

以上代码在g++ 4.9, g++ 5.1clang 3.5c++11c++14都产生错误(即使f从未实例化)。

为什么这是不允许的,一般规则是什么?我认为没有理由限制这一点。这似乎是一个非常奇怪的禁令。

至于为什么不将第一个变量写成x_fix_t,更清楚的是x_t有一个强制性的第一个参数。(例如,这就是f()不被允许的原因)。但这并不重要,解决方法很简单。问题仍然是:为什么?

gcc错误:

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

叮当声错误:

error: pack expansion used as argument for non-pack parameter of
alias template   x_t<Args...> v2;

在GCC 4.8中编译,但在GCC 4.9中失败,这证明它与CWG 1430和bug报告#59498有关。Roy Chrihfield提出的修复方法与您的完全相同:

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;

此外,Jason Merrill详细阐述了为什么它会失败:

事实上,不,这是一个Core 1430问题;没有办法损坏variadic_alias<T…比;没有提到别名的名字模板在变形,他们应该是完全透明的。这只在4.8中偶然有效,因为检查是版本禁用。>

在bug报告中没有进一步的讨论,所以我们可以求助于CWG 1430:

最初,包扩展不能扩展为固定长度模板参数列表,但在N2555中更改了。这是对于大多数模板都可以,但是会导致别名模板的问题。

在大多数情况下,别名模板是透明的;当它用在模板中,我们只需要替换相关的模板参数即可。但是,如果模板id使用包扩展,则无法工作non-variadic参数。例如:
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...>);

没有办法表达A<Ts…>用S表示,所以我们需要保持到A上,直到我们有t来代入,因此需要在缠绕中处理。

目前,EDG和Clang拒绝这个测试用例,并抱怨a.g ++的一些模板参数也是如此,但我认为那是一个错误。然而,在ABI名单上,约翰·斯派塞(John Spicer)认为应该是这样拒绝。

(另见第1558期)

2012年10月会议纪要:

CWG一致认为这种用法应该被禁止,当依赖参数不能使用别名模板时,禁止使用别名模板直接替换为类型id。

2013年4月附加说明:

再举一个例子:

  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>({});
  }

在这个例子的处理中存在实现差异。

换句话说,这是一个持续存在的问题,没有任何解决方案(AFAIC)。

这是一个令人讨厌的不便,但有一个解决方案:

使using模板接受单个typename T,并使调用者将可变参数包装在types_t容器中

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

,然后,从现在接受types_tusing,委托在另一个元函数

中展开和使用可变参数的工作。