我很惊讶以下代码导致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)
解决问题。为什么编译器不能从默认参数中推断出类型?
我很惊讶以下代码导致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)
解决问题。为什么编译器不能从默认参数中推断出类型?
在 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):
未推断的上下文是:
...
- 一个模板形参,用于函数形参的形参类型,该形参具有一个默认实参,该实参在进行实参推导的调用中使用。
一般来说,要实现这一点会遇到一些技术困难。请记住,模板中的默认参数只有在需要时才会实例化。那么考虑一下:
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);
今天通过执行(除其他外)以下步骤来解决此问题:
为了从默认参数进行推断,该默认参数必须在完成推断过程之前自行实例化。这可能会失败,导致 SFINAE 上下文之外的错误。即,可能完全不适合通话的候选人可能会触发错误。
一个很好的理由可能是
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);
即使写成一个,它仍然像两个功能合二为一,在任何意义上都不是“首选”。您正在调用单参数函数;两个参数的一个实际上是一个不同的函数。默认参数表示法只是将它们合并为一个。
如果重载具有您所要求的行为,那么为了保持一致性,它必须在模板被分成两个定义的情况下工作。这是没有意义的,因为这样推论就会从一个没有被调用的不相关函数中提取类型!如果没有实现,则意味着重载不同的参数列表长度与“默认参数”相比成为“二等公民”。
如果重载和默认值之间的区别对客户端完全隐藏,那就太好了。