实现模板操作员非好友

Implement template operator non-friend

本文关键字:好友 操作员 实现      更新时间:2023-10-16

我有一个简单的泛型算术向量类,想要实现标量乘法的*运算符:

template<class Value_T, unsigned int N>
class VectorT
{
public:
    typedef Value_T value_type;
    typedef unsigned int size_type;
    size_type GetNumElements() const { return N; }      
    // class member
    // elements in the vector
    value_type elements[N];     
    // the number of elements
    static const size_type size = N;
};
// scalar multiplication
template<class Value_T, unsigned int N>
const VectorT<Value_T, N> operator*(const VectorT<Value_T, N>& v, Value_T s)        
{
    VectorT<Value_T,N> vTemp(v);
    for (unsigned int i = 0; i < v.GetNumElements(); i++)
    {
        vTemp.elements[i] *= s;
    }
    return vTemp;
}

像这样使用它。。。

typedef VectorT<double, 3> Vec3;
int main(int argc, char* argv[])
{
    Vec3 v;
    Vec3 v2 = v * 2; // multiply with scalar int
    return 0;
}

给出编译器错误C2782(MSVC2012),表示*运算符的模板参数Value_T不明确:int或double。

如果我将类中的*运算符定义为友元函数,那么错误就消失了,它对标量int或double也很有效。但实际上这里不需要友元声明,因为类VectorT的公共接口对于运算符来说就足够了(在我的真实代码中,我有成员privat和一些公共访问器方法)。

我希望标量乘法只适用于Value_T类型:对于VectorT<int,N>我只想给*运算符一个整数值。我进一步希望*算子是可交换的。这就是为什么我不把它实现为一个简单的成员函数,其中左手操作符的类型总是VectorT。

如何在这里实现*操作符作为非成员和非好友?

查看操作员对的定义

const VectorT<Value_T, N> operator*(const VectorT<Value_T, N>& v, Value_T s) 

和在线

Vec3 v2 = v * 2; // multiply with scalar int

运算符*是用VectorT和int类型的参数调用的。因此Value_T一次是double,另一次是int。您可以乘以2.0来解决歧义,或者在运算符*定义中添加第三个模板参数:

template<class Value_T, unsigned int N, class ScalarType>
const VectorT<Value_T, N> operator*(const VectorT<Value_T, N>& v, ScalarType s)

到目前为止发布的答案建议为函数参数s的类型提供一个独立的模板参数。这是一种可行的方法,但不是唯一的方法。

或者,一个更好的解决方案可能是通过有意将第二个参数放入非推导上下文,将其从模板参数推导过程中排除

// scalar multiplication
template<class Value_T, unsigned int N>
const VectorT<Value_T, N> operator*(const VectorT<Value_T, N>& v, 
                                    typename VectorT<Value_T, N>::value_type s)        
{
  ...
}

这样,编译器将从v参数的类型推导模板参数,而不是从s参数的类型。s仍然会有正确的类型,就像你希望的那样

上面的方法利用了这样一个事实,即类模板已经提供了一个方便的内部typename value_type。在更一般的情况下,可以通过使用identity模板来实现同样的目的

template <typename T> struct identity
{
  typedef T type;
};
template<class Value_T, unsigned int N>
const VectorT<Value_T, N> operator*(const VectorT<Value_T, N>& v, 
                                    typename identity<Value_T>::type s)        
{
  ...
}

C++标准库决定不包含std::identity模板,因为std::decaystd::remove_reference显然涵盖了它的功能。因此,通过使用标准模板,您可以将通用解决方案实现为

template<class Value_T, unsigned int N>
const VectorT<Value_T, N> operator*(const VectorT<Value_T, N>& v, 
                                    typename std::decay<Value_T>::type s)        
{
  ...
}

下面的文章提到了这个具体的问题http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3766.html

首先,您可以使用std::array而不是实现VectorT。然而,您遇到的问题是由于表达式Vec3 v2 = v * 2;中的积分文字2属于int类型。因此,编译器无法推导出您的重载operator*,因为在实现时,它仅在VectorT包含与乘法器类型相同的元素的情况下有效。

为了克服这一点,您可以向重载的operator*添加一个额外的模板参数,如下例所示:

template<class Value_T1, class Value_T2, unsigned int N>
VectorT<Value_T1, N> operator*(const VectorT<Value_T1, N>& v, Value_T2 s)        
{
    VectorT<Value_T1, N> vTemp(v);
    for(unsigned int i(0), sz(v.GetNumElements()); i < sz; ++i) {
        vTemp.elements[i] *= s;
    }
    return vTemp;
}

现场演示