c ++ - 实现模板操作符非朋友

时间:2014-10-12 18:29:47

标签: c++ templates operator-overloading

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

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&lt; int,N&gt;我只希望给*运算符赋值。我还希望*运算符是commutativ。这就是为什么我不将它作为一个简单的成员函数实现的,其中左手操作符始终是VectorT类型。

如何在这里将*运营商作为非会员和非朋友实施?

3 个答案:

答案 0 :(得分:4)

查看运营商的定义

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类型的参数调用

operator *。所以Value_T是一次加倍,另一次是另一次。您可以乘以2.0来解决歧义,或者将第三个模板参数添加到operator * definition:

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

答案 1 :(得分:3)

到目前为止发布的答案建议为函数参数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仍然会有正确的类型,就像你想要的那样。

上述方法利用了这样一个事实,即您的类模板已经提供了方便的内部类型名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

答案 2 :(得分:2)

首先,您可以使用std::array代替实施VectorT。然而,您遇到的问题是由于表达式2中的整数文字Vec3 v2 = v * 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;
}

LIVE DEMO