提问者:小点点

C比较运算符重载常量与非常量行为


我最近注意到一些我自己无法理解的运算符重载行为。以下两个类仅在ClassA的成员比较运算符重载上的const不同。在ClassB中,它们不是const。通常我知道人们总是更喜欢const之一,但我仍然对为什么我们会看到我将在下面描述的行为感兴趣。

#include <string>

class ClassA {
public:
    explicit ClassA(double t) : _t(t) {}

    std::string operator<=(int const& other) const {
        return "A(<=)";
    }

    std::string operator==(int const& other) const {
        return "A(==)";
    }

    friend std::string operator<=(int const& other, ClassA const& expr) {
        return "A'(<=)";
    }

    friend std::string operator==(int const& other, ClassA const& expr) {
        return "A'(==)";
    }

private:
    double _t;
};

class ClassB {
public:
    explicit ClassB(double t) : _t(t) {}

    std::string operator<=(int const& other) {
        return "B(<=)";
    }

    std::string operator==(int const& other) {
        return "B(==)";
    }

    friend std::string operator<=(int const& other, ClassB const& expr) {
        return "B'(<=)";
    }

    friend std::string operator==(int const& other, ClassB const& expr) {
        return "B'(==)";
    }

private:
    double _t;
};

现在我想在const和非const场景中使用这些类和比较函数。

int
main(int argc,
     char* argv[]) {
    ClassA a1{0};
    1==a1; //OK
    1<=a1; //OK
    ClassA const a2{0};
    1==a2; //OK
    1<=a2; //OK
    ClassB b1{0};
    1==b1; //NOT OK
    1<=b1; //OK
    ClassB const b2{0};
    1==b2; //OK
    1<=b2; //OK

    return 0;
}

一切正常,但我标记为Not OK的一行除外。这会引发编译器错误。

error C2446: '==': no conversion from 'ClassB' to 'int'

我的问题分为三个部分,但我希望有一个很好的理由来回答所有这些问题。所以我希望把这个问题放在一个SO问题中仍然没有问题。

为什么等号操作符==不编译,当不等式

  • 在评论中@Eljay指出,问题可能是由一个新的C 20功能造成的,该功能会自动生成带有倒排参数的比较运算符。这显然使成员std::字符串运算符==(int const
  • 对于四个关系运算符表达式x

在所有情况下,在重写表达式的上下文中不考虑重写的候选集。对于所有其他运算符,重写的候选集是空的。

>

  • @463035818_is_not_a_number指出了一些关于不同编译器的有趣发现,它们可以和不能编译不同版本的代码。特别是对于带有-std=c 2a标志的clanggcc,最新版本的x86-64 clang 12.0.0x86-64 gcc 11.1不能编译,而旧版本的x86-64 clang 9.0.1x86-64 gcc 9.4可以编译。对于VisualStudio,我们看到类似的模式,标志为/std: c最新。这里最新版本x64 msvc v19.28(VS16.9)不编译,而直接前身x64 msvc v19.28编译。这些测试是用编译器资源管理器godbolt.org的。

    特别有趣的是,注意到clanggcc的编译器错误表明问题在于std::字符串操作符==(int const

    叮当

    error: return type 'std::string' (aka 'basic_string<char>') of selected 'operator==' function for rewritten '==' comparison is not 'bool'
        1==b1; //NOT OK
    

    海合会

    error: return type of 'std::string ClassB::operator==(const int&)' is not 'bool'
        1==b1; //NOT OK
    

    虽然这些都是非常有趣的见解,但最初的问题仍然悬而未决。


  • 共1个答案

    匿名用户

    不是一个具体的答案。但是,让我们看看留档。

    重载运算符的返回类型没有限制(因为返回类型不参与重载解析),但有规范实现:

    …该语言对重载运算符的作用或返回类型没有其他限制,但一般来说,重载运算符的行为应尽可能与内置运算符相似

    然后:

    …返回类型受预期使用运算符的表达式的限制。

    例如,赋值操作符通过引用返回以使写入a=b=c=d成为可能,因为内置运算符允许这样做。

    我们进一步挖掘:

    …其中内置运算符返回bool,大多数用户定义的重载也返回bool,以便用户定义的运算符可以以与内置相同的方式使用。但是,在用户定义的运算符重载中,任何类型都可以用作返回类型(包括void)。

    甚至更进一步(三方比较):

    如果两个操作数都具有算术类型,或者如果一个操作数具有无范围枚举类型而另一个具有整数类型,则通常的算术转换将应用于操作数。

    所以,我会断言它取决于实现。在我的机器上,它编译(g)并运行:

    std::cout << (1==b1) << std::endl; // Prints B'(==)
    

    @463035818_is_not_a_number:“这个问题出现在VS中。较新版本的gcc也拒绝这种用法,clang也是如此。它看起来更像是一个bug/缺失的功能,并在最近的版本中得到了修复。”

    这是带有该问题的编译器资源管理器片段。

    相关问题