我在模板类中有多个 * 运算符的重载。当我将声明按不同顺序放置时,为啥会得到不同的结果?

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&lt;typename U&gt; friend Example&lt;U&gt; operator*(U, Example&lt;U&gt;); 这解决了问题,但我想知道为什么第一个版本编译第二个没有(在所有主要编译器上)。 对于发布的示例实例化,在两个实例中都使用了转换构造函数。所以它充其量是模棱两可的。但是,错误消息没有多大意义。关键字explicit 可能有助于诊断。 @MarekR 我尝试按照您的建议更改声明,但如果我将朋友声明放在另一个声明下方,我仍然会收到错误。 【参考方案1】:

您的代码中有两个名为operator* 的不同事物。有你在Example 之前声明的函数模板和Example 的成员函数。语法operator*&lt;T&gt; 是模板特化,仅当operator* 引用模板而不是成员函数时才有效。在您的第一个声明中,friend 位于成员函数之前,operator* 成员函数尚未在编译器看到 operator*&lt;T&gt; 时声明,因此它将名称解析为在 @987654330 之前声明的函数模板@ 一切都很好(具体的专业化 operator*&lt;T&gt; 变成了每个 Example&lt;T&gt;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*&lt;T&gt; 用于引用不是模板的成员函数,并且您会遇到语法错误(具体来说,我认为它试图以某种方式将其解释为operator* &lt; T &gt;,其中&lt;&gt; 是实际的小于/大于运算符)。

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&lt;&gt; 他没有的前缀! @MarekR It does not. 您需要 template&lt;&gt; 前缀来声明模板的特化,但我们并没有真正这样做。 你写过关于“模板专业化”的文章,我只是引用你,并指出问题没有它。所以我同意你的观点:“我们并没有真正这样做”。你的这个链接也没有这样做,所以我不知道这是什么意思。【参考方案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&lt;double&gt;) 成为friendExample&lt;int&gt;?那有必要吗? (显然,非成员 operator* 可以通过将其实现更改为使用成员 operator* 来编写,而无需成为 friend,但假设我们想保留 friend 版本但使其更加简洁。)跨度> @NathanPierson 我相信你说的是对的,也许这会带来不便。如果你有更好的想法,一定要告诉。 我认为这个问题是“为什么?”,而不是“如何修复代码?”。这个答案只是我在一个问题下的评论。 问题是为什么,但找到一些有用的代码似乎是一种奖励。我们为什么不分享呢?

以上是关于我在模板类中有多个 * 运算符的重载。当我将声明按不同顺序放置时,为啥会得到不同的结果?的主要内容,如果未能解决你的问题,请参考以下文章

如何在具有动态大小数组的模板类中重载 operator=

尝试在模板类中重载 / 运算符的 C++ 错误

c ++模板类中的运算符重载

逆向第十九讲——类继承和成员类运算符重载模板逆向20171211

重载运算符的 C++ 前向声明

重载赋值运算符 - 多态容器