使用模板进行隐式类型转换

Implicit type conversion with template

本文关键字:类型转换      更新时间:2023-10-16

我有一个模板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)

解决方案已经显示在此答案中。现在,更多关于问题...

代码中的问题是重载解析是如何执行的。当考虑将模板函数用于重载解析时,编译器将对参数执行类型推断,并提出与调用匹配的类型替换,否则它将无法应用该模板,将其从潜在候选集合中删除并继续。此时的问题是类型推导仅推断出完全匹配(可能具有额外的常量/易失性限定(。由于匹配是精确的,编译器不会使用任何转换(同样,除了 cv(。

最简单的例子发生在std::maxstd::min函数中:

unsigned int i = 0;
std::min( i, 10 );    // Error! 

类型推导将推断出template <typename T> min( T const &, T const & )中的 T 对于第一个参数unsigned,但对于第二个参数int它们不同,编译器将丢弃此模板函数。

答案中提出的解决方案是使用该语言的一个功能,使您能够在类定义中定义非成员友元函数。模板的优点是,对于模板的每个(不同的(实例化,编译器将在命名空间级别创建一个免费的非模板函数,该函数具有通过在友元声明中替换实例化的实际类型而获得的签名:

template <typename T>
class test {
    friend test operator+( test const & lhs, test const & rhs ) {  // [1]
        return test();
    }
}
test<int> t;                                                       // [2]

在上面的示例中,编译器允许您在类作用域 [1] 中添加 friend 函数的定义。然后当你在 [2] 中实例化模板时,编译器将生成一个自由函数:

test<int> operator+( test<int> const & lhs, test<int> const & rhs ) { 
   return test<int>();
}
无论您

是否使用它,该函数都是始终定义的(这与按需实例化的模板类成员函数不同(。

这里的魔力有多个方面。第一部分是,通常您为每个和所有实例化类型定义非模板函数,因此您可以获得通用性,同时获得重载解析的优势,当参数不完全匹配时能够使用此函数。

因为它是一个非模板函数,所以编译器能够对两个参数调用隐式转换,你将得到预期的行为。

此外,查找还有一种不同类型的魔法,因为如此定义的函数只能通过参数依赖查找找到,除非它也在命名空间级别声明,在我们的例子中,这不能以通用方式完成。这意味着可能是好是坏,这取决于你想如何考虑它......

因为它只能由 ADL 找到,所以除非至少有一个参数已经是所需的类型(即它永远不会用于执行对两个参数的转换(,否则不会考虑它。缺点是,除非实际调用函数,否则无法引用该函数,这意味着您无法获取函数指针。

(此处详细介绍了模板友谊,但请注意,在这种特殊情况下,所有其他变体将无法执行隐式转换(。

每次尝试使用模板提供运算符至少需要一秒钟的重载。 但是你可以通过在类中定义运算符来避免这种情况:

template <unsigned int m>
class A
{
public:
  A(int) {}
  inline friend A operator+(const A& a, const A& b) { return A(0); }
};

适用于a+55+a

添加此运算符

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;

}

您可以尝试在A类的模板中添加一个额外的"policy"类型参数,该参数将确定实际所需的转化类型。 例如:

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。