25

我很惊讶以下代码导致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<float>(5)解决问题。为什么编译器不能从默认参数中推断出类型?

4

3 回答 3

20

在 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):

未推断的上下文是:

...

  • 一个模板形参,用于函数形参的形参类型,该形参具有一个默认实参,该实参在进行实参推导的调用中使用。
于 2012-03-09T04:36:47.127 回答
16

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

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 上下文之外的错误。即,可能完全不适合通话的候选人可能会触发错误。

于 2015-09-04T17:27:21.317 回答
4

一个很好的理由可能是

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);

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

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

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

于 2012-03-09T04:55:02.273 回答