【问题标题】:Why can't the compiler deduce the template type from default arguments?为什么编译器不能从默认参数中推断出模板类型?
【发布时间】:2012-03-26 14:29:35
【问题描述】:

我很惊讶以下代码导致了could not deduce template argument for T 错误:

struct foo
{
  template <typename T>
  void bar(int a, T b = 0.0f)
  {
  }
};

int main()
{
  foo a;
  a.bar(5);

  return 0;
}

致电a.bar&lt;float&gt;(5) 可解决此问题。为什么编译器不能从默认参数中推断出类型?

【问题讨论】:

    标签: c++ templates default-arguments


    【解决方案1】:

    在 C++03 中,规范明确禁止使用默认参数来推导模板参数(C++03 §14.8.2/17):

    模板类型参数不能从函数默认参数的类型推导出来。

    在 C++11 中,您可以为函数模板提供默认模板参数:

    template <typename T = float>
    void bar(int a, T b = 0.0f) { }
    

    不过,默认模板参数是必需的。如果没有提供默认模板参数,则默认函数参数仍然不能用于模板参数推导。具体来说,以下适用(C++11 14.8.2.5/5):

    未推断的上下文是:

    ...

    • 在函数形参的形参类型中使用的模板形参,该形参有一个默认实参,该实参在进行实参推导的调用中使用。

    【讨论】:

    • 虽然说“因为标准是这样说的”是一个有效的答案,但很高兴知道它背后的原因。
    • 除其他原因外,函数的不同声明可以声明不同的默认参数(我相当肯定这同样适用于函数模板。)
    • @James:不,不同的声明不允许声明不同的默认参数。甚至不允许多个声明为同一个参数提供相同的默认值。 8.3.6 说“一个默认参数不应该被以后的声明重新定义(即使是相同的值)。”当然,这只适用于非模板函数。对于模板函数,看起来默认参数只能在初始声明中提供。
    • 除了默认模板参数之外的另一种解决方法是使用第二个函数调用第一个函数并使用适当的默认值inline void bar(int a){ bar(a, 0.0f);}
    【解决方案2】:

    一般来说,要实现这一点会有一些技术上的困难。请记住,模板中的默认参数只有在需要时才会实例化。然后考虑:

    template<typename T, typename U> void f(U p = T::g());  // (A)
    template<typename T> T f(long, int = T());  // (B)
    int r = f<int>(1);
    

    今天通过执行(除其他外)以下步骤解决了这个问题:

    1. 尝试为候选 (A) 和 (B) 推断模板参数; 这对 (A) 失败,因此被消除。
    2. 执行重载决议; (B) 被选中
    3. 形成调用,实例化默认参数

    为了从默认参数进行推断,该默认参数必须在完成推断过程之前自行实例化。这可能会失败,导致 SFINAE 上下文之外的错误。即,可能完全不适合通话的候选人可能会触发错误。

    【讨论】:

    • 对我来说听起来很合理。
    • 所以编译器可以让这种情况不可行。不用把婴儿和洗澡水一起扔出去。
    【解决方案3】:

    一个很好的理由可能是

    void foo(bar, xyzzy = 0);
    

    类似于一对重载。

    void foo(bar b) { foo(b, 0);  }
    foo(bar, xyzzy);
    

    此外,有时将其重构为这样是有利的:

    void foo(bar b) { /* something other than foo(b, 0); */ }
    foo(bar, xyzzy);
    

    即使写成一体,它仍然像两个功能合二为一,在任何意义上都不是“首选”。您正在调用单参数函数;两个参数的一个实际上是一个不同的函数。默认参数表示法只是将它们合并为一个。

    如果重载具有您所要求的行为,那么为了保持一致性,它必须在模板被分成两个定义的情况下工作。那是没有意义的,因为这样推论就会从一个没有被调用的不相关函数中提取类型!如果没有实现,那就意味着重载不同的参数列表长度成为“二等公民”,而不是“默认参数”。

    如果重载和默认之间的区别对客户端完全隐藏,那就太好了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-23
      • 1970-01-01
      • 2020-09-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多