为啥这个嵌套的可变参数模板是无效参数?

Posted

技术标签:

【中文标题】为啥这个嵌套的可变参数模板是无效参数?【英文标题】:Why is this nested variadic template an invalid argument?为什么这个嵌套的可变参数模板是无效参数? 【发布时间】:2015-07-29 08:27:58 【问题描述】:

如果我定义一个接受模板参数的struct 模板Bar

template <template <int,bool,char> class>
struct Bar ;

我可以使用struct 模板实例化它,例如Zod

template <int,bool,char> struct Zod ;
Bar<Zod> a;

我也可以使用嵌套的struct 模板来实例化它,例如JKL

struct GHI 
  template <int,bool,char>
  struct JKL ;
;
Bar <GHI::JKL> b;

为什么我不能使用嵌套的可变参数struct 模板(例如DEF)来实例化Bar?:

template <typename ...Ts>
struct ABC 
  template <Ts ...>
  struct DEF ;
;

Bar<ABC<int,bool,char>::DEF> c;

G++ 4.9.2 抱怨类型/值不匹配;而 Clang 3.4.2 的错误报告模板模板参数的模板参数与其对应的模板模板参数不同。

【问题讨论】:

在 gcc 5.1.0 或 clang 3.6.0 上也不能编译,因为它的价值。 @vsoftco ABC&lt;int,bool,char&gt;::DEF&lt;4,true,'c'&gt; foo; 工作。 棘手的部分是DEF 实际上采用了非类型模板参数包。请参阅 [temp.param]/p15 中的示例。 @Barry 我的意思是DEF 在技术上是通过非类型参数包参数化的。并且带有包的模板模板参数与不带包的模板模板参数不匹配。 @AndyProwl 这无关紧要。它仍然是一个参数包,这就是 §14.3.3/3 不允许匹配的原因。 【参考方案1】:

让我们给 DEF 的参数包起一个名字以便于参考:

template <typename ...Ts>
struct ABC 
  template <Ts ... Values>
  struct DEF ;
;

这里的重点是,通过 [temp.param]/p15,Ts... ValuesbothTs 的包扩展和参数包 Values 的声明。 p>

如果一个 template-parameter 是 [...] 一个 parameter-declaration 声明一个参数包(8.3.5),那么 template-parameter 是一个 模板参数包 (14.5.3)。模板参数包是 parameter-declaration 其类型包含一个或多个未扩展的参数包是包扩展。

由于DEF采用非类型参数包,它与不带包的模板模板参数不匹配([temp.arg.template]/p3):

一个 template-argument 匹配一个模板 template-parameter P 当 中的每个模板参数 template-argument对应的类模板或别名模板A的template-parameter-list匹配对应的模板 P的template-parameter-list中的参数。两个模板参数 如果它们属于同一类型(类型、非类型、模板),则匹配,对于 非类型模板参数,它们的类型是等价的(14.5.6.1), 而对于模板template-parameter,它们的每一个对应的 模板参数的匹配,递归。当 P 的 template-parameter-list 包含模板参数包(14.5.3)时,模板参数包会匹配零个或多个模板 中的参数或模板参数包 A的template-parameter-list与P中的模板参数包的类型和形式相同(忽略那些模板是否 参数是模板参数包)。

可以肯定的是,Values 对于包来说是相当奇怪的 - 对于 ABC 的每个特化,Values 必须包含固定数量的参数 - 但在当前规则下它仍然是一个包,所以包的规则申请。

【讨论】:

我想我理解你的解释,但我仍然有不清楚的地方。特别是,您指的是哪些“包装规则”?请注意,您引用的段落的最后一部分是指相反的情况(即,当 parameter 而不是参数是一个包时)。引用的第一部分提到如果相应的模板参数匹配,则模板模板参数匹配模板模板参数,并且它们可以是三种:“类型”、“非类型”和“模板”。我没有看到“packs”的特殊规则,“pack”也不是一种参数 @AndyProwl 对。后半部分表示如果参数中有一个包,它可以匹配参数中的包和非包。默认情况下,如果参数不是包,那么它不能匹配参数中的包,因为没有任何东西说参数中的(非包)模板参数可以匹配参数中的参数包。 使用template&lt;template&lt;int...&gt; class&gt; struct ;Bar&lt;ABC&lt;int,int,int&gt;::DEF&gt; c;,它可以编译。但是他们都必须是ints。因此,在匹配模板模板参数时,编译器似乎会区分模板参数包和 3 个模板参数...【参考方案2】:

就像巴里之前所说的:

ABC<int,bool,char>::DEF<4,true,'c'> foo

并尝试在此站点Compiler 上使用 Coliru 在线编译器 gcc 5.1 c++14:

#include <iostream>
 template <template <int,bool,char> class>
struct Bar ;
template <int,bool,char> struct Zod ;

Bar<Zod> a;

struct GHI 
  template <int,bool,char>
  struct JKL ;
;

Bar <GHI::JKL> b;

template <template <typename... Ts> class>
struct Base ;
template<typename... Ts>
struct Floor ;
Base<Floor> c;

template <typename... Ts>
struct ABC 
  template <Ts... val>
  struct DEF ;
;
ABC<int,bool,char>::DEF<4,true,'c'> foo;

我搜索了一下,找到了这个模板参数列表。

包扩展可能出现在模板参数列表中:

 template<typename... T> struct value_holder

    template<T... Values> // expands to a non-type template parameter 
    struct apply  ;     // list, such as <int, char, int(&)[5]>
;

我在运行代码编译器中测试了一些东西: http://en.cppreference.com/w/cpp/language/parameter_pack 但我也在visual studio 2013中找到了这个Ellipses and Variadic Templates: https://msdn.microsoft.com/en-us/library/dn439779.aspx

【讨论】:

以上是关于为啥这个嵌套的可变参数模板是无效参数?的主要内容,如果未能解决你的问题,请参考以下文章

为啥编译器不能通过逗号运算符扩展可变参数模板的参数?

为啥我不能将模板函数指针传递给可变参数函数?

第23课 可变参数模板_Optional和Lazy类的实现

如何使用可变参数模板参数保存可变数量的参数?

PHP 的可变长度参数 `...` 标记应该被称为啥?

一脚踩进java之基础篇34——可变参数Collection工具类集合嵌套