【问题标题】:Why don't default parameters work alongside a parameter pack?为什么默认参数不能与参数包一起使用?
【发布时间】:2020-04-28 07:40:16
【问题描述】:

因为8.3.6 ([dcl.fct.default])/4

在给定的函数声明中,带有默认实参的形参后面的每个形参都应在此声明或先前声明中提供默认实参,或者应为函数形参包。

以下应该编译:

#include <iostream>

template<typename ...Ts>
void foo(int i=8, Ts... args)
{
    std::cout << "Default parameters \t= " << i << "\n";
    std::cout << "Additional params  \t= " << sizeof...(Ts) << "\n";
}

int main()
{
    foo();                  // calls foo<>(int)
    foo(1, "str1", "str2"); // calls foo<const char*, const char*>(int, const char*, const char*)
    foo("str3");            // ERROR: does not call foo<const char*>(int, const char*)

    return 0;
}

但由于foo("str3") 混淆了编译器,它无法编译。它抱怨没有匹配的函数来调用foo(const char*),并且它无法将"str3"(类型const char*)转换为类型int

我知道可以通过函数重载或使用命名参数习语(参见where to place default value parameter in variable-length function in c++?default arguments and variadic functions)来解决这个问题。但是,我想知道编译器是否只是愚蠢的,或者是否有真正的原因导致上面代码示例中的预期行为没有实现。换句话说,为什么即使我将函数显式实例化为foo&lt;const char*&gt;(int, const char*),编译器也会抱怨?就好像显式实例化只是忽略了默认参数的值。为什么?

【问题讨论】:

  • 您引用的段落仅适用于声明,不适用于函数调用。所以函数声明确实应该编译(它确实),但最后一次调用没有(并且没有理由应该)。此外,您没有在任何地方执行显式实例化。最后,添加参数包并不意味着您可以忽略可选参数的位置要求...
  • @MaxLanghof 是的,上面的代码没有明确的实例化。但是我对上述显式实例化进行了同样的尝试。它不编译。鉴于如果我将默认参数放在参数包的右侧,它也不会编译,在我看来,可变参数函数中默认参数的概念根本不存在。是这样吗?
  • 当然存在。您的前两个函数调用表明它工作得很好。您实际上可以将两个可选的东西(可选参数和可能为空的参数包)放在同一个函数中,但这并不能让您摆脱关于参数位置的约束。如果您的函数参数以可选参数开头,则您要么根本不提供参数,要么必须为可选参数提供值。后续参数是可变参数还是可选参数对此没有任何影响。

标签: c++ variadic-templates default-value function-templates explicit-instantiation


【解决方案1】:

具有默认值的参数仍然是位置的。你的例子是一样的

void foo(int i=8, const char * c="hello world")
{
    std::cout << "Default param \t= " << i << "\n";
    std::cout << "Additional param \t= " << c << "\n";
}

int main()
{
    foo();                  // 8 "hello world"
    foo(1, "str1");         // 1 "str1"
    foo("str3");            // ERROR: parameter mismatch

    return 0;
}

【讨论】:

    【解决方案2】:

    这不起作用的原因与它不起作用的原因相同:

    void f(int = 0, const char*);
    
    int main() {
        f("test")
    }
    

    如果您阅读该错误,您会看到编译器无法将字符串文字转换为int。这是真的,第一个参数是一个 int,你给它发送了一个字符串字面量。

    参数不能被反弹到其他位置,无论是否有默认参数。

    【讨论】:

      【解决方案3】:

      你引用的标准只是说形成是合法的

      template<typename ...Ts>
      void foo(int i=8, Ts... args)
      

      当你调用它时,你仍然必须传递一个int 作为第一个参数,否则在重载解析期间将不会考虑该函数。当你这样做时

      foo("str3");
      

      编译器将查找任何采用const char*const char(&amp;)[5] 的函数foo,因为这是唯一的参数。这意味着您的函数完全被忽略了,因为它需要 int 作为第一个参数或根本没有参数。

      【讨论】:

      • 该声明的合法性是否仅允许调用foo()?这里的所有都是它的?谢谢。
      • @NanashiNoGombe 差不多。如果没有该豁免,即使声明 void foo(int i=8, Ts... args) 也是非法的,因为第一个默认参数之后的所有参数也需要默认,并且无法为参数包提供默认值。
      猜你喜欢
      • 2013-05-31
      • 2018-04-30
      • 1970-01-01
      • 2014-07-18
      • 2020-05-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多