首页 文章

c - 实现模板操作员非朋友

提问于
浏览
2

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

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

3 回答

  • 4

    到目前为止发布的答案建议为函数参数 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

  • 3

    看看运营商的定义

    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 *定义:

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

    首先,您可以使用 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;
    }
    

    LIVE DEMO

相关问题