转换逻辑目标是什么类型

What type does the conversion logic target?

本文关键字:是什么 类型 目标 转换      更新时间:2023-10-16

我不明白为什么在以下代码中表达式 C c3 = 5 + c;尽管可以像上一个语句中的5个转换为C。

一样
#include <iostream>
class C 
{
    int m_value;
public:
    C(int value): m_value(value) {} ;
    int get_value() { return m_value; } ; 
    C operator+(C rhs) { return C(rhs.m_value+m_value); }
};
int main()
{
    C c = 10;
    C c2 = c + 5; // Works fine. 5 is converted to type C and the operator + is called
    C c3 = 5 + c; // Not working: compiler error. Question: Why is 5 not converted to type C??
    std::cout << c.get_value() << std::endl; // output 10
    std::cout << c2.get_value() << std::endl; // output 15
}

,因为如果超载操作员作为类的成员函数,则只有在该类的对象用作左操作数时才能调用。(而左操作数变为要调用成员函数的隐式*this对象。)

二进制运算符通常作为非成员实现以维护对称性(例如,在添加复杂的数字和整数时,如果操作员 是复杂类型的成员函数,则只有complex+integer会编译,而不是integer+complex)。

根据标准,[over.match.oper]/3

(强调我的)

对于一个单一操作员 @,带有CV UNQUALIFIED的类型操作数 版本是T1,适用于带有左操作数的二进制操作员 类型的CV UNCALIFIFIFIFIFIED版本是T1和类型的正确操作数 其CV UNCALIFIED版本是T2,四组候选功能, 指定的成员候选人,非成员候选人,内置 候选人和重写的候选人的构建如下:

  • (3.1)如果T1是当前定义的完整类类型或类别的类 T1 ::操作员的查找([over.call.func]);否则,一组 会员候选人为空。

这意味着,如果左操作数的类型不是类类型,则成员候选人的集合为空;不考虑超载运算符(作为成员函数)。

您可以将其作为非成员功能重载,以允许左右操作的隐式转换。

C operator+(C lhs, C rhs) { return C(lhs.get_value() + rhs.get_value()); }

然后c + 55 + c都可以正常工作。

live

btw:这将导致一个临时对象被构造(从 intC),以调用非会员函数;如果您关心的话,可以添加所有三个可能的过载,如下所示。另请注意,这是一个权衡问题。

C operator+(C lhs, C rhs) { return C(lhs.get_value() + rhs.get_value()); }
C operator+(C lhs, int rhs) { return C(lhs.get_value() + rhs); }
C operator+(int lhs, C rhs) { return C(lhs + rhs.get_value()); }

,这里有一些有关何时使用普通,朋友或成员函数过载

的建议

在大多数情况下,该语言使您决定是否确定是否 您想使用普通/朋友或成员功能版本 超载。但是,两者中的一个通常比 其他。

与不修改左操作数的二进制操作员打交道 (例如操作员 ),通常是普通或朋友函数版本 首选,因为它适用于所有参数类型(即使 左操作数不是班级对象,也不是一类 可修改)。普通或朋友功能版本已添加 "对称"的好处,因为所有操作数变为明确的参数 (而不是左操作数变成 *和右操作数 成为明确的参数)。

与确实修改左操作数的二进制操作员打交道时 (例如操作员 =),通常首选成员函数版本。 在这些情况下,最左的操作数将始终是类型,并且 使对象被修改成为 *这是 * 自然的。因为最右操作数变成了明确的参数,所以 对于谁被修改和谁得到的人来说,没有任何困惑 评估。

您面临的理由将某些操作员过载定义为免费功能,即需要在需要隐式转换时。要查看引擎盖下发生的事情,请考虑操作员超载调用的详细形式:

C c2 = c.operator+(5); // Ok, c has this member function
C c3 = 5.operator+(c); // No way, this is an integer without members

您显然可以做的是

中的明确C构造
C c3 = C{5} + c;

,但这不是针对C之类的算术值类型。要使隐式结构成为可能,请定义超载为免费功能

auto operator + (C lhs, const C& rhs)
{
    lhs += rhs;
    return lhs;
}

现在,左侧操作数没有限制。请注意,操作员是根据+=实施的(您必须实现它以进行上述代码编译),这是本主题中指出的好习惯:当您为自定义类型提供二进制operator +时,类型还可以预期operator +=可用。因此,为了减少代码重复,通常用+=实现+(对于所有其他算术操作数相同)。

进一步指出,这些操作数通常需要大量的样板代码。为了减少这一点,请考虑例如提升操作员库。为了生成所有标准算术运算符,基于最小的实际手写代码:

#include <boost/operators.hpp>
class C : private boost::arithmetic<C>
//                ^^^^^^^^^^^^^^^^^^^^
//                where the magic happens (Barton-Nackmann trick)
{
   int m_value ;
   public:
     C(int value): m_value(value) {} ;
     C& operator+=(const C& rhs) { m_value += rhs.m_value; return *this; }
     C& operator-=(const C& rhs) { m_value -= rhs.m_value; return *this; }
     C& operator*=(const C& rhs) { m_value *= rhs.m_value; return *this; }
     C& operator/=(const C& rhs) { m_value /= rhs.m_value; return *this; }
     const C& operator+() const { return *this; }
     C operator-() const { return {-m_value}; }
     int get_value() { return m_value; } ;
};

这是关于您的建议,您建议编译器可以隐式将左手参数转换为 C的另一种说明("还原ad vassurdum"),从本质上讲,基本上会打开一个can蠕虫。简单地说,实际的语言规则说,在应用转换之前,要查找函数调用和呼叫(用户指定)运算符的名称查找 - 是为了找到候选人集。在这一点上,尚未考虑操作数类型,但是范围非常好。因此,只要用户指定运算符才在范围内,第一个参数的类型 do 是在范围内,当它的第一个参数是(CV-qualified)类型的类型。当候选人设置时,已经发现,编译器试图应用转换规则并对候选人进行排名。

(因此,您的问题有些误导,因为在您的示例中,我们甚至都不了解转换逻辑,而是已经空了名称。)

现在,想象一下,我们可以简单地更改语言,说第一个参数也可以在名称分辨率之前转换。这里需要一点手势,因为这意味着我们必须进行转换,查找名称,然后再进行转换,因此在实践中的工作方式肯定不清楚。无论如何,然后查看此示例:

struct B;
struct A
{
    A(int);
    A operator +(B) const;
};
struct B
{
    B(int);
    B operator +(B) const;
};

现在,1 + B{3}应该做什么?显然,它可以转换为B{1} + B{3}。但是谁说我们不能做A{1} + B{3}呢?为什么B的构造方法比A的s首选?当然,我们可以说要么是B是首选,因为,看看B{...}+B{...}的良好和对称性(好的,我有点熟悉)。或者,我们可以采取更安全的途径,说该程序包含这种模棱两可的话,该程序是不明式的。但是还有更多的角案件要考虑,例如如果将B的构造函数制成explicit - 编译器(仍在或新的)错误应该出现,还是应该默默切换到可用的隐式转换为A

另一个不明确的观点是哪种范围(例如命名空间)的类型?如果您在例如中使用operator +,那肯定会令人惊讶全局名称空间范围,编译器会挖掘某种类型的__gnucxx::__internal::__cogwheels::__do_something_impl,请暗示操作数,然后对其进行操作。

还要注意,即使可以以合理和干净的方式指定该功能,也可能具有相当多的编译时间成本(实际上,在编译C 和原因之一时,超载分辨率已经是最大的成本之一。为什么编译C 代码比编译c)要长得多。

tl; dr:

  • 有棘手的角案。
  • 好处是边际(为什么不像其他人指出的那样使此类操作员自由职能)?
  • 关于如何标准化这一点的讨论肯定会很长。