在下面的代码中,我有一个非成员模板函数,并且对类型int
进行了完全专业化。
#include <iostream>
template <typename U>
void f(const U& x, const U& y)
{
std::cout << "generic " << x << " " << y << std::endl;
}
template <>
void f(const int& x, const int& y)
{
std::cout << "specialization int " << x << " " << y << std::endl;
}
int main()
{
int a = 1;
f(a, a);
f('a', 'a');
f('a', 1); // Compiler error
// f<int>('a', 1); // This compiles
return 0;
}
尽管该语言提供了从char
到int
的隐式转换,但编译器(g ++ 7.3.0和clang 6.0.1)不会编译代码,从而产生错误>
error: no matching function for call to ‘f(char, int)’
deduced conflicting types for parameter ‘const U’ (‘char’ and ‘int’)
虽然很清楚为什么模板推导不起作用,但我不清楚为什么编译器在丢弃通用模板后不考虑隐式转换。例如,如果我用f
显式实例化U=int
并取消注释代码中的相应行,则为
f<int>('a', 1);
然后代码编译并正确给出输出
specialization int 1 1
generic a a
specialization int 97 1
如果相反,我用<{1}}的重载来补充代码,而不是模板专用化为
f
然后代码编译并给出预期的输出
#include <iostream>
template <typename U>
void f(const U& x, const U& y)
{
std::cout << "generic " << x << " " << y << std::endl;
}
void f(const int& x, const int& y)
{
std::cout << "overload int " << x << " " << y << std::endl;
}
int main()
{
int a = 1;
f(a, a);
f('a', 'a');
f('a', 1);
return 0;
}
总结:为什么隐式转换对重载有效,但对看似等效的模板专门化却无效?
答案 0 :(得分:4)
当编译器看到此内容时:
f('a', 1);
由于有两种选择,因此无法推断类型:
f(const char &, const char &);
f(const int &, const int &);
由于您的模板的两个参数的类型相同。
这两种选择都同样有效,没有合理的规则可以解决这种歧义。因此,编译器必须报告错误,以避免不良行为。请注意,静默类型转换对此问题没有影响,而且您对模板的专业化也无法帮助解决此问题。
std::max
会出现相同的问题。
现在的问题是,您确定第二个参数更重要并且应该对模板参数类型产生影响吗?如果是,那么您可以强制忽略第一个参数的类型(免责声明:这是不寻常且意外的,因此将来的代码维护可能容易出错)。
template <typename T>
struct Identity {
using type = T;
};
// note C++20 introduces std::type_identity
template<typename T>
void f(const typename Identity<T>::type& x, const T& y)
{
std::cout << "generic " << x << " " << y << std::endl;
}
在这种形式下,第一个参数将不参与模板的类型推导,因为它取决于第二个参数的类型。
现在这个
f('a', 1);
将编译,因为第二个参数将导致T=int
并且第一个参数类型应与第二个参数相同。现在可以执行静默转换。
答案 1 :(得分:1)
为什么隐式转换对重载有效,但对看似等效的模板专门化却无效?
因为模板特化正是一种特化。
因此,当您具有模板功能和模板专长时,您可以编写
f(x, y);
编译器推断出x
和y
的类型。
当且仅当推导的类型相同时,请考虑模板函数,并且当且仅当类型为int
(对于两个参数)时,选择特殊化。
致电时
f<someType>(x, y);
您说编译器:“忽略类型推导,并调用将f()
强制为someType
的模板函数T
”。
在这种情况下,写作
f<int>('a', 1);
编译器选择模板特化并将a
转换为int
。
使用非模板函数的情况有所不同,因为它始终可用,并且编译器仅需验证所有参数是否等于或可转换为该函数的参数。