【问题标题】:Parameter pack must be at the end of the parameter list... When and why?参数包必须在参数列表的末尾...何时以及为什么?
【发布时间】:2023-04-07 22:34:01
【问题描述】:

如果参数列表绑定到一个类,我不明白参数包必须位于参数列表末尾的原因,而如果参数列表是成员方法声明的一部分,则放宽约束。

换句话说,这个编译:

class C {
    template<typename T, typename... Args, typename S>
    void fn() { }
};

以下一项没有:

template<typename T, typename... Args, typename S>
class C { };

为什么第一种情况被认为是正确的,而第二种情况则不是?
我的意思是,如果它是合法的语法,不应该在这两种情况下都适用吗?

要清楚,真正的问题是我定义了一个类似于以下的类:

template<typename T, typename... Args, typename Allocator>
class C { };

将分配器类型作为最后一种类型将不胜感激,但我可以以某种方式解决它(无论如何,如果您有任何建议,我们将不胜感激,也许您的建议比我的要优雅得多!)。
也就是说,我得到了错误:

参数包'Args'必须在模板参数列表的末尾

所以,我只是想完全理解为什么它在某些情况下被接受,但在其他一些情况下却不是。

Here 是一个类似的问题,但它只是解释了如何解决问题,这对我来说很清楚。

【问题讨论】:

  • 您使用的是最后一个类型参数的默认值——据我回忆,这改变了很多。 (但我忘记了细节)。或许您应该在两个开场示例中都包含默认值?
  • 您能举例说明您打算如何调用这些模板吗?我不确定它是否非常相关,但它可能会有所帮助
  • 对不起,默认参数出错,不再存在。我打算像使用其他模板一样使用该模板:C&lt;int, float, std::allocator&lt;int&gt;&gt;。问题在于该语法适用于成员方法,但不适用于类定义,我不明白为什么不这样做!

标签: c++ templates c++11 variadic-templates


【解决方案1】:

它对函数模板有效,但只有当参数推导可以帮助编译器解析模板参数时,你的函数模板示例实际上是无用的,因为

template<typename T, typename... Args, typename S> void fn() { }
int main() { fn<int, int, int>(); }
test.cpp: In function 'int main()':
test.cpp:2:32: error: no matching function for call to 'fn()'
 int main() { fn<int, int, int>(); }
                                ^
test.cpp:1:57: note: candidate: template<class T, class ... Args, class S> void fn()
 template<typename T, typename... Args, typename S> void fn() { }
                                                         ^
test.cpp:1:57: note:   template argument deduction/substitution failed:
test.cpp:2:32: note:   couldn't deduce template parameter 'S'
 int main() { fn<int, int, int>(); }

编译器无法确定哪些模板参数属于参数包,哪些属于S。实际上就像@T.C.指出它实际上应该是一个语法错误,因为以这种方式定义的函数模板永远无法实例化。

一个更有用的函数模板是这样的

template<typename T, typename... Args, typename S> void fn(S s) { }

因为现在编译器能够明确地将函数参数s 与模板类型S 匹配,其副作用是S总是被推导出 - 所有显式模板参数第一个之后将属于Args

这些都不适用于(主要)类模板,不会推导出参数并且明确禁止:

来自草案 n4567

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4567.pdf

[温度参数] / 11

[...]如果主类模板或别名的 模板参数 template 是一个模板参数包,应该是最后一个 模板参数.[...]

(如果它们被推导出来,就会像函数模板示例中那样模棱两可)。

【讨论】:

  • 嗯,好吧,似乎问题中的示例已经减少了太多,但是指出如何使它工作或不工作的好点。所以这是合法的语法,都是关于演绎的,没有办法像问题中那样定义一个类,我必须找到一个合理且可用的解决方案......有什么想法吗?如问题中所述,将不胜感激。 :-)
  • @skypjack 我认为你需要先解释一下你到底想要做什么,例如为什么Allocator 需要放在最后。
  • @skypjack 如何将参数打包成自己的类,例如 tuple
  • @skypjack 如果函数类型对最终用户有意义,也许是template&lt;class F, class Alloc&gt; struct meow; template&lt;class Ret, class... Args, class Alloc&gt; struct meow&lt;Ret(Args...), Alloc&gt; { };?
  • @skypjack 你应该问 T.C.要将其添加到他们的答案中,我不想将别人的想法归功于 :)
【解决方案2】:

第一个是不对的。编译器有问题,无法诊断。 [temp.param]/11:

不应遵循函数模板的模板参数包 由另一个模板参数,除非该模板参数可以 从函数模板的 parameter-type-list 推导出来或具有 默认参数 (14.8.2)。


如果函数类型 T(Args...) 对最终用户有意义,解决此问题的一种方法是改用部分特化:

template<class F, class Alloc> class C; //undefined
template<class T, class... Args, class Alloc>
class C<T(Args...), Alloc> {
    // implementation
};

根据实际需求,对分配器进行类型擦除可能也值得考虑。

【讨论】:

  • 编译器是 GCC5,如果不存在这个问题,我应该在他们的错误跟踪系统上开一张票吗?
  • 类型擦除分配器是什么意思?我知道类型擦除是一个成语,但我不明白你指的是什么。
  • @skypjack 基本上,std::functionstd::promisestd::packaged_task 做了什么。您在构造函数中获取分配器并对其进行类型擦除,以便您的类型不依赖于分配器的类型。
  • 好的,知道了,建议你把它从模板的参数列表中去掉。好的提示,我不确定这对于我正在处理的真实代码来说是一个可行的解决方案,但我会考虑它。谢谢。
  • 你应该链接 #15 而不是 #11。
猜你喜欢
  • 2012-12-01
  • 1970-01-01
  • 2020-02-14
  • 2016-12-02
  • 1970-01-01
  • 1970-01-01
  • 2021-03-06
  • 2022-10-24
  • 1970-01-01
相关资源
最近更新 更多