C++模板类友元运算符重载

4

我正在尝试创建一个通用的向量类,既是为了我的代码片段库,也是为了练习模板类。 本质上,向量类是模板化的,允许您选择其精度为float、double、long double等。

我遇到的问题是重载*运算符以缩放向量。去除所有正常工作的重载和成员函数后,类定义如下:

#pragma once

#include <math.h>   //  for sqrt function when normalizing

template <typename T> class Vector;
template <typename T> Vector<T> operator*(const Vector<T>& obj);

template <typename T> class Vector {
private:
    //  Attributes:
    static const int DIMS = 3;
    T component[DIMS];

public:
    enum { 
        X, Y, Z
    };

public:
    //  Constructors:
    Vector(void) {
        for (int i=0; i<DIMS; ++i) {
            component[i] = T();
        }
    }
    Vector(T x, T y, T z) {
        component[X] = x;
        component[Y] = y;
        component[Z] = z;
    }

    //  Destructor:
    ~Vector(void) { }

    //  Scaling:
    friend Vector<T> operator*(const Vector<T>& obj);
    Vector operator*(const T scale) {
        Vector<T> result = Vector<T>();

        for (int i=0; i<DIMS; ++i) {
            result.component[i] = component[i] * scale;
        }

        return result;
    }
};

template <typename T>
Vector<T> operator*(const Vector<T>& obj) {
    Vector<T> result = Vector<T>();

    for (int i=0; i<DIMS; ++i) {
        result.component[i] = obj.component[i] * this*;
    }

    return result;
}

在我的主方法中,我有以下代码行: ```java ```
Vector<float> testVector1 = Vector<float>(1.0f, 0.0f, 0.0f);
Vector<float> testVector2 = Vector<float>(0.0f, 1.0f, 0.0f);
Vector<float> testVector3 = 10.0f * testVector1;
Vector<float> testVector4 = testVector2 * 10.0f;

除了一个错误,一切都编译正常:在main()函数中,第四行(将向量乘以标量)可以正常工作,但第三行(将标量乘入向量)却给我报错:
错误 1 error C2677: binary '*' : no global operator found which takes type 'Vector<T>' (or there is no acceptable conversion)
我对这个问题的最佳猜测是编译器不知道我试图重载哪个基本类型的*运算符,而我无法直接告诉它,因为在模板被传递到类之前,类不会知道类型。有没有办法实现我想做的事情,或者模板必须始终跟随类进行运算符重载?
更新: 感谢jwismar和其他人指出的错误左手重载尝试。现在类内部函数的定义为:
friend Vector<T> operator*(T scalar, const Vector<T>& obj);

它的实现方式是:

template <typename T> 
Vector<T> operator*(T scalar, const Vector<T>& obj) {
    Vector<T> result = Vector<T>();

    for (int i=0; i<DIMS; ++i) {
        result.component[i] = obj.component[i] * scalar;
    }

    return result;
}

在类之上的重载初始声明现在是 template <typename T> Vector<T> operator*(T scalar, const Vector<T>& obj);,然而无论是否将其注释掉,我都得到了相同的错误。
现在我面临一个更具体的问题,涉及模板和运算符重载。尽管错误现在是未解决的外部调用,编译器仍然不予通过: Error 1 error LNK2019: unresolved external symbol "class Vector<float> __cdecl operator*(float,class Vector<float> const &)" (??D@YA?AV?$Vector@M@@MABV0@@Z) referenced in function _main C:\Users\D03457489\Desktop\UVCTester\UVCTester\main.obj UVCTester 因此编译器告诉我它能够找到 operator*(float, Vector<float>) 的定义,但找不到实现。那么新的问题是:这是我的另一个基本疏忽的结果,还是使用模板以这种方式生成运算符重载不可能使操作数左侧的结果未知?

但是这个结果也没有回答我的问题,因为它与我试图解决的情况无关:当左侧是未知类型时,重载运算符由于涉及到模板类而变得困难。我已经知道如何重载非模板类。 - J W
我非常清楚运算符重载的基础知识,谢谢。我也明白,在我最初尝试实现它时,我犯了一个简单的错误,但是我已经纠正了这个错误。这正是多人协作调试以及整个网站的意义所在。它不是为了对其他用户的研究水平进行无用(和毫无根据的)评估,并将他们的问题视为缺乏努力的表现,以使自己感觉更加优越。 - J W
就像任何其他函数一样。如果您需要一个与任何类型匹配的参数,请使用模板,其中包含表示该函数参数类型的模板参数。运算符重载只是提供一个带有特殊名称的函数,这个名称将被编译器捕获。为什么下面的答案没有被接受?(我可以猜到,但您应该真正减少代码到一个特定的示例并重新发布问题。提示:友元声明不会成为模板的朋友,而是自由函数,请参见此处,但更正上面的错误) - David Rodríguez - dribeas
也许不是,但你肯定是唯一一个应该为你的敌对和过度假设负责的人。你在整个讨论中都在纠缠于技术细节,而不是帮助解决问题。如果你如此坚定地以一种反生产力的态度来处理问题,我真的不知道你为什么会在这里。 - J W
只有在我回复了一个只包含“你应该研究一下,而不是在这里发帖”的帖子后,你才尝试提供有用的建议。即使你这样做了,你的建议也与实际问题无关。当我指出这一点时,你只是争辩说它们是相关的,而不是试图理解我的问题。在你的大多数帖子中,我不禁感到你并没有认真阅读问题,而更感兴趣的是吸引人们关注你的其他帖子;你所有的“帮助”都是链接到你自己编写的无关答案。 - J W
显示剩余12条评论
4个回答

1

错误不在第四行,而是在第三行。编译器正在寻找一个以浮点数为左操作数的内置函数,但它找不到。您需要定义一个自由函数,例如:

template <typename T> Vector<T> operator*(T scalar, const Vector<T>& obj);

就像我在原始问题中所说的那样,我知道导致错误的是第三行。无论如何,我已经按照您的建议更改了重载的定义和实现,现在我得到了以下错误:Error 1 error LNK2019: unresolved external symbol "class Vector<float> __cdecl operator*(float,class Vector<float> const &)" (??D@YA?AV?$Vector@M@@MABV0@@Z) referenced in function _main C:\Users\D03457489\Desktop\UVCTester\UVCTester\main.obj UVCTester我认为这是朝着正确方向迈出的一步,但现在编译器似乎不想将定义与实现匹配。 - J W
抱歉,我误读了您认为有错误的那一行。我以为看到的是“主函数中的第四行给我带来了一个错误。” 现在,您遇到的是链接器错误而不是编译器错误。这意味着编译器对您拥有的函数声明非常满意,但无法找到您已经声明的某个函数的定义。 在这种情况下,它找不到模板函数实例,其中typename T = float。 - jwismar
尝试为float添加一个明确的operator*实例化。您添加的声明将如下所示: template Vector<float> operator*<float>(float scalar, const Vector<float>& obj); - jwismar

1

operator* 是一个二元运算符,它需要两个参数,这意味着您可以将其实现为应用于左侧并接受一个参数(右侧)的成员函数,也可以将其实现为接受两个参数的自由函数。通常最好将其实现为自由函数。特别是当您想要能够实现 double*Vector<T> 时,因为左侧不是一个类。该重载的签名将是:

template <typename T>
Vector<T> operator*( T d, Vector<T> v );

你可能想要添加的内容:

template <typename T>
Vector<T> operator*( Vector<T> v, T d );

0
尝试使用以下函数签名(未经测试):
template <typename T, typename F>
Vector<T> operator*(const F &lhs, const Vector<T>& rhs);

它应该允许像这样的语句:

auto vec = f * other_vec;

对于任何具有定义的类型 F 和以下运算符的类型:

template <typename F, typename T>
undetermined operator*(const F& lhs, const T &rhs);

其中T是用于Vector<T>的类型,而返回的类型可以隐式转换为T

因此,以下内容可能有效:

long l;
float f;
int i;
//.......

Vector<float> v1;
v2 = l * v1;
v3 = f * v2;
v4 = i * v3;

这个方案是可行的;我可以为float、double和long double重载一个版本,并想出一种聪明的方法来处理在重载实现中向各种精度的转换。不过,我更感兴趣的是找到一个更通用的基于模板的解决方案,但说实话,我甚至不确定是否可能。 - J W
这样行吗?如果可以的话请告诉我。现在手头没有编译器。 - Anthony
成功将它作为"template <typename F> friend Vector<T> operator*(F scalar,const Vector<T>& obj)"运行,并采用内联实现。由于某种原因,将实现从类定义中分离出来会导致编译器抱怨操作符不明确并且被实现了两次:它指向在声明函数为friend的类中的定义,以及在结尾处的实现,并认为它们都是实现。无论如何,我已经让它足够满足我的需求,所以感谢您的时间和耐心! - J W
@JW:考虑查看此答案,了解在模板内部如何声明友元、不同的选项和语义。链接 - David Rodríguez - dribeas
@JW,既然您接受了这个答案,那么点个赞呗?;) - Anthony

0
你需要定义一个函数,允许在 * 左侧使用标量。幸运的是,这相对容易实现:
template <typename T>
Vector<T> operator*(const T scale, const Vector<T> &v) {
    Vector<T> result = v;
    return result * scale;
}

你的另一个执行向量分量相乘的函数声明和实现不正确。它不需要成为友元:

/*friend*/ Vector<T> operator*(const Vector<T>& obj);

然后你将其实现为类的一个方法:

template <typename T>
Vector<T> Vector<T>::operator*(const Vector<T>& obj) {
    Vector<T> result = Vector<T>();

    for (int i=0; i<DIMS; ++i) {
        result.component[i] = obj.component[i] * component[i];
    }

    return result;
}

我没有一个函数可以对两个“向量”进行逐分量乘法。我有一个点积和叉积函数,它们没有在问题的代码中列出,但这两个函数都可以正常工作并正确实现。我认为你把“向量向量”与用于标量乘法的重载混淆了。 - J W
@JW:抱歉,当我没有完全理解代码时,我会对我阅读的代码进行假设。您提供的源代码清单中的最后一个函数无法编译,似乎不合适,如果它应该执行叉积,则需要返回矩阵而不是向量。问候。 - jxh
是的,类定义之外实现的函数是我第一次尝试重载operator*(T, Vector<T>)时不正确的实现。根据jwismar的建议,我已经进行了更改,现在正在尝试解决一个相关的问题。我可以理解这可能会让人感到困惑;回想起来,它看起来像是我想要将两个向量相乘。在半相关的语义中,两个向量的叉积返回一个垂直于两个源向量的向量,而不是矩阵。虽然,在技术上,一个向量可以被视为是一个一维的矩阵,所以你的说法也没错。:) - J W
@JW:糟了!你是对的,我总是有这个行列式的视觉形象,并一直认为它是一个矩阵。敬礼。 - jxh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接