我有一个模板class A
template <unsigned int m>
class A
{
public:
A(int) {}
};
其中包含int
的构造函数。我有一个手术:
template<unsigned int m>
A<m> operator+(const A<m>&, const A<m>&)
{
return A<m>(0);
}
但是当我打电话时:
A<3> a(4);
A<3> b = a + 5;
A<3> c = 5 + a;
我希望int
隐式转换为A,但编译器会抛出错误。
是否有任何优雅的方法可以在不使用以下解决方案的情况下启用隐式转换:
a + A<m>(5)
operator+<3>(a, 5)
答案 0 :(得分:36)
该解决方案已在this answer中显示。现在,更多关于这个问题...
代码中的问题是如何执行重载决策。当考虑模板函数进行重载解析时,编译器将对参数执行类型推导,并提出与调用匹配的类型替换,否则无法应用该模板,将其从潜在候选集中移除并继续。此时的问题是类型推导仅推导出完全匹配(可能具有额外的const / volatile限定)。因为匹配是精确的,所以编译器不会使用任何转换(再次,除了cv)。
最简单的例子就是std::max
和std::min
函数:
unsigned int i = 0;
std::min( i, 10 ); // Error!
类型推导会将template <typename T> min( T const &, T const & )
中的T推导为第一个参数的unsigned
,但int
推导出它们不同的第二个参数,编译器将丢弃此模板函数。
answer中提出的解决方案正在使用该语言的一项功能,使您能够在类定义中定义非成员友元函数。模板的优点是,对于模板的每个(不同的)实例化,编译器将在命名空间级别创建一个免费的非模板函数,该函数具有通过替换友元声明中的实例类型的实际类型而获得的签名:
template <typename T>
class test {
friend test operator+( test const & lhs, test const & rhs ) { // [1]
return test();
}
}
test<int> t; // [2]
在上面的例子中,编译器允许你在[1]的类范围内添加友元函数的定义。然后,当您在[2]中实例化模板时,编译器将生成一个自由函数:
test<int> operator+( test<int> const & lhs, test<int> const & rhs ) {
return test<int>();
}
无论您是否使用该函数,该函数都定义为始终(这与根据需要实例化的模板类成员函数不同)。
magic 这里有多个方面。第一部分是一般您为每个和所有实例化类型定义非模板函数,因此您获得了通用性,同时重载解析的优势是能够使用此函数当论证不完美匹配时。
因为它是非模板函数,所以编译器能够在两个参数上调用隐式转换,并且您将获得预期的行为。
此外,不同类型的 magic 继续查找,因为这样定义的函数只能通过参数依赖查找找到,除非它也在命名空间级别声明在我们的例子中,不能以通用的方式完成。这可能是好的还是坏的,取决于你想如何考虑它......
因为它只能由ADL找到,所以除非至少有一个参数已经是所需的类型(即它永远不会用于执行转换到两个参数),所以不会考虑它。缺点是除非你实际上调用它,否则不可能引用该函数,这意味着你无法获得函数指针。
(有关模板友谊的更多信息here,但请注意,在此特定情况下,所有其他变体将无法执行隐式转换)。
答案 1 :(得分:18)
每次尝试使用模板提供操作员都需要至少一次第二次过载。但是你可以通过在类中定义运算符来避免这种情况:
template <unsigned int m>
class A
{
public:
A(int) {}
inline friend A operator+(const A& a, const A& b) { return A(0); }
};
适用于a+5
和5+a
。
答案 2 :(得分:1)
添加此运算符
template<unsigned int m>
A<m> operator+(const A<m>&, const int&)
{
return A<m>(0);
}
或试试这个
template <unsigned int m>
class A
{
friend const A operator+(const A& a, const A& b) { return A(0); }
public:
A(int) {}
// OR FOR UNARY
// const A operator+(const A &a) const {return A(0);}
};
int main(){
A<3> a(4);
A<3> b = a + 5;
A<3> c = 5 + a;
}
答案 3 :(得分:0)
您可以尝试为A
类的模板添加一个额外的“策略”类型参数,以确定实际所需的转换类型。例如:
template <unsigned int m, typename ConvVal = int>
class A
{
public:
typedef ConvVal conv_val;
A(ConvVal) {}
};
template<template <unsigned int, class U> class T, unsigned int m, typename U>
T<m, U> operator+(const T<m, U>&, const T<m, U>&)
{
return T<m, U>(0);
}
template<template <unsigned int, class U> class T, unsigned int m, typename U>
T<m, U> operator+(const T<m, U>&, const typename T<m, U>::conv_val&)
{
return T<m, U>(0);
}
template<template <unsigned int, class U> class T, unsigned int m, typename U>
T<m, U> operator+(const typename T<m, U>::conv_val&, const T<m, U>&)
{
return T<m, U>(0);
}
int main()
{
A<3> a(4);
A<3> b = a + 5;
return 0;
}
现在,您的A
类将采用默认为int
类型的其他模板参数,并定义允许自动转换的实际类型。您只需要重复operator+
函数三次,一次是针对没有转换值的版本,一个类型为A<m, T>
的显式类,另一个是operator+
的两个版本采取转换类型。在上面的代码中,我使用更多泛型类型对此进行了概括,以便可以使用具有适当模板签名的任何其他类来完成此操作,并定义conv_val
typedef。