我在模板类中有多个 * 运算符的重载。当我将声明按不同顺序放置时,为啥会得到不同的结果?
Posted
技术标签:
【中文标题】我在模板类中有多个 * 运算符的重载。当我将声明按不同顺序放置时,为啥会得到不同的结果?【英文标题】:I have multiple overloads of the * operator in a template class. Why do I get different results when I put the declarations in different order?我在模板类中有多个 * 运算符的重载。当我将声明按不同顺序放置时,为什么会得到不同的结果? 【发布时间】:2021-08-09 03:32:17 【问题描述】:我有一个需要多个 * 运算符重载的类。其中一些需要声明为朋友,以便我可以将类类型作为第二个参数。这是一个遇到我将要介绍的问题的类的示例:
#pragma once
template<typename T>
class Example;
template<typename T>
Example<T> operator*(T value, Example<T> obj);
template<typename T>
class Example
private:
T m_data;
public:
Example(T);
friend Example operator*<T>(T, Example);
Example operator*(const T& other);
;
template<typename T>
Example<T> operator*(T value, Example<T> obj)
return value * obj.m_data;
template<typename T>
Example<T>::Example(T data) :m_data(data)
template<typename T>
Example<T> Example<T>::operator*(const T& other)
return Example(m_data * other.m_data);
这可行,但如果我改变:
template<typename T>
class Example
private:
T m_data;
public:
Example(T);
friend Example operator*<T>(T, Example);
Example operator*(const T& other);
;
与
template<typename T>
class Example
private:
T m_data;
public:
Example(T);
Example operator*(const T& other);
friend Example operator*<T>(T, Example);
;
即使我所做的只是交换包含运算符重载声明的那两行,我也开始收到一堆错误。你能解释一下这里发生了什么吗?这对我来说毫无意义。
产生错误的代码:
Example<int> a(2);
2 * a;
错误:
unexpected token(s) preceding';' syntax error missing':' before '<' 'operator*': redefinition: previous definition was 'function' 'operator*': looks like a function but there is no parameter list. '*': uses 'Example<int>' which is being defined '*': friend not permitted on data declarations
https://godbolt.org/z/j4zYTP8n7
【问题讨论】:
最好在问题中包含错误消息。如果您发布确实导致错误的代码(然后解释如何更改以使其工作),问题会更清楚 在您的会员operator*
中,return Example(m_data * other.m_data);
不应该是return m_data * other;
以与您的非会员operator*
保持一致吗?操作数的顺序不应影响结果
应该是:template<typename U> friend Example<U> operator*(U, Example<U>);
这解决了问题,但我想知道为什么第一个版本编译第二个没有(在所有主要编译器上)。
对于发布的示例实例化,在两个实例中都使用了转换构造函数。所以它充其量是模棱两可的。但是,错误消息没有多大意义。关键字explicit
可能有助于诊断。
@MarekR 我尝试按照您的建议更改声明,但如果我将朋友声明放在另一个声明下方,我仍然会收到错误。
【参考方案1】:
您的代码中有两个名为operator*
的不同事物。有你在Example
之前声明的函数模板和Example
的成员函数。语法operator*<T>
是模板特化,仅当operator*
引用模板而不是成员函数时才有效。在您的第一个声明中,friend
位于成员函数之前,operator*
成员函数尚未在编译器看到 operator*<T>
时声明,因此它将名称解析为在 @987654330 之前声明的函数模板@ 一切都很好(具体的专业化 operator*<T>
变成了每个 Example<T>
的 friend
)。
template<typename T>
Example<T> operator*(T value, Example<T> obj); // <-\ ...finds a template, so no syntax error
// |
template<typename T> // |
class Example // |
T m_data; // |
public: // |
Example(T); // |
friend Example operator*<T>(T, Example); // >-/ looking up this name...
Example operator*(const T& other);
;
换一种方式,而是将operator*<T>
用于引用不是模板的成员函数,并且您会遇到语法错误(具体来说,我认为它试图以某种方式将其解释为operator* < T >
,其中<
和 >
是实际的小于/大于运算符)。
template<typename T>
class Example
T m_data;
public:
Example(T);
Example operator*(const T& other); // <-\ ...does not find a template; ouch!
friend Example operator*<T>(T, Example); // >-/ looking up this name...
;
【讨论】:
template specialization
需要template<>
他没有的前缀!
@MarekR It does not. 您需要 template<>
前缀来声明模板的特化,但我们并没有真正这样做。
你写过关于“模板专业化”的文章,我只是引用你,并指出问题没有它。所以我同意你的观点:“我们并没有真正这样做”。你的这个链接也没有这样做,所以我不知道这是什么意思。【参考方案2】:
在您的帮助下,我找到了以下解决方案。我还能够摆脱第一行声明的冗余。这是最终产品:
template<typename T>
class Example
private:
T m_data;
public:
Example(T);
Example operator*(const T& other);
template<typename T>
friend Example<T> operator*(T, Example<T>);
;
template<typename T>
Example<T> operator*(T value, Example<T> obj)
return value * obj.m_data;
template<typename T>
Example<T>::Example(T data) :m_data(data)
template<typename T>
Example<T> Example<T>::operator*(const T& other)
return Example(m_data * other.m_data);
【讨论】:
我的理解是否正确,例如,这将使operator*(double, Example<double>)
成为friend
的Example<int>
?那有必要吗? (显然,非成员 operator*
可以通过将其实现更改为使用成员 operator*
来编写,而无需成为 friend
,但假设我们想保留 friend
版本但使其更加简洁。)跨度>
@NathanPierson 我相信你说的是对的,也许这会带来不便。如果你有更好的想法,一定要告诉。
我认为这个问题是“为什么?”,而不是“如何修复代码?”。这个答案只是我在一个问题下的评论。
问题是为什么,但找到一些有用的代码似乎是一种奖励。我们为什么不分享呢?以上是关于我在模板类中有多个 * 运算符的重载。当我将声明按不同顺序放置时,为啥会得到不同的结果?的主要内容,如果未能解决你的问题,请参考以下文章