【问题标题】:Function template parameter pack not at the end of the parameter list函数模板参数包不在参数列表末尾
【发布时间】:2016-12-02 10:42:53
【问题描述】:

以下代码编译运行正常。

void foo() {

}

template <typename T, typename... Args>
void foo(T x, Args... args) {
  cout << x << endl;
  foo(args...);
}

// inside main()
foo(1,1,1);

其他代码无法编译:

void foo() {

}

template <typename... Args, typename T>
void foo(Args... args, T x) {
  foo(args...);
  cout << x << endl;
}

// inside main()
foo(1,1,1);

编译器说没有匹配的函数来调用foo(1,1,1) 并说foo(Args... args, T x) 是候选,但模板参数推导/替换失败,因为候选需要1 个参数,但提供了3 个。

这种情况是否存在任何编译器无法处理的歧义?这个编译错误对我来说似乎不合逻辑。也许这不符合 C++ 标准?

【问题讨论】:

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


    【解决方案1】:

    Clang's error message 有趣的部分是:

    main.cpp:11:6: note: candidate template ignored: couldn't infer template argument 'T'
    
    void foo(Args... args, T x) {
         ^
    

    问题在于参数包Args...先于出现在T

    Args... 是“贪婪的”,因此没有任何参数可供编译器推导出 T,因此它会失败。

    引用标准(强调我的):

    [temp.param]/11

    函数模板的模板参数包不得遵循 由另一个模板参数除非该模板参数可以 从函数模板的参数类型列表推导出或具有 默认参数。 [示例:

    ...
    // U can be neither deduced from the parameter-type-list nor specified
    template<class... T, class... U> void f() { } // error
    template<class... T, class U> void g() { } // error
    

    ——结束示例]

    【讨论】:

    • 所以这只是“编译器没有正确实现标准”的情况?
    • @matheuscscp,不,标准也不允许。
    • @chris 有什么具体原因吗?
    • 我不得不对此投反对票,因为它是错误的。实际上,Args... 并不贪婪地消耗参数。由于它不是在最后,它不消耗参数并且大小为0(未推导的参数包将回退到大小为零)。这意味着T 变为int 并且其余两个参数没有匹配的参数进行推导。错误消息“因为候选人需要 1 个参数,但提供了 3 个。”是正确的。然而,Clang 的错误信息是不正确的。
    • @songyuanyao 它在 14.8.1p3 中。基本上,参数包不能推导出来。它们“默认”为空。当然,模板参数包“typename ... Args”不是“尾随”(IMO 将 template 参数包限制为尾随,而不是 函数参数包虽然..奇怪的限制,如果你问我)。目的是按描述工作,请参阅open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1399open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1388
    【解决方案2】:

    (此答案基于@JohannesSchaub-litb's comments

    根据标准,模板形参包如果用在不在形参列表末尾的函数形参包中,则不可推演。

    §14.8.2.1/1 Deducing template arguments from a function call [temp.deduct.call]:

    当函数参数包出现在非推导上下文中时 ([temp.deduct.type]),该参数包的类型从不 推断。 [ 例子:

    template<class T1, class ... Types> void g1(Types ..., T1);
    
    void h(int x, float& y) {
      const int z = x;
      g1(x, y, z);                 // error: Types is not deduced
      g1<int, int, int>(x, y, z);  // OK, no deduction occurs
    }
    

    —结束示例]

    关于非推断上下文,§14.8.2.5/5 Deducing template arguments from a type [temp.deduct.type]

    没有出现在参数声明列表末尾的函数参数包。

    所以foo(1,1,1);失败的直接原因是没有推导出模板参数Args,这是使函数调用有效所必需的。

    为了解释错误信息,一个未推导的模板参数包将被推导为一个空的模板参数序列[1],这意味着它将被省略。然后foo(1,1,1); 失败,因为参数的数量不匹配,这就是编译器抱怨的。

    正如标准中所示的示例,您可以显式指定模板参数以避免类型推断,即使它不符合代码的原始意图。如:

    template <typename T, typename... Args>
    void foo(Args... args, T x) {
    }
    
    int main() {
        // inside main()
        foo<int, int, int>(1, 1, 1);
    }
    

    Here'是一些附加信息。


    [1] 我在标准中找不到关于此的直接表达。最接近的一个是this,“一个尾随模板参数包([temp.variadic])没有被推导出来,将被推导出为一个空的模板参数序列。”

    【讨论】:

      猜你喜欢
      • 2012-12-01
      • 1970-01-01
      • 2023-04-07
      • 2019-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-26
      相关资源
      最近更新 更多