为什么C++不使用运算符==而不是运算符!=自动

Why doesn't C++ use operator== instead of operator!= automatically

本文关键字:运算符 自动 C++ 为什么      更新时间:2023-10-16

我知道C++不能为类自动定义operator==,但当operator!=不可用而operator==可用时,为什么它不能为a != b使用!(a == b)

我知道std::rel_ops,尽管我今天之前没有听说过它。

因为operator==并不一定意味着与operator!=相反。

我想不出有哪个例子operator==不是指!operator!=,但它们是独立的运算符。C++最解放、有时也最令人沮丧的事情之一是,C++对如何编写代码应用了一组最小的限制。如果您有一个实例,其中operator==不是operator!=的对立面,那么您应该能够用C++表达它。事实上,你可以。

在C++中,你可以以偏概全。你可能会认为这是"坏的"。

请记住,在绝大多数情况下,根据operator==正确实现operator!=是微不足道的。

bool Gizmo::operator!=(const Gizmo& rhs) const
{
return !operator==(rhs);
}

C++作为一种语言不提供您没有明确要求的功能。我知道这种哲学与默认构造函数等有点脱节,但这是Stroustrup很早就做出的一个设计决定-你不会为你不使用的东西付费。所以编译器不会自动生成你没有要求的东西。

1993年初,ACCU网站上引用了Bjarne的一条电子邮件链,其中提到了这一点。它也在D&E如果我回忆正确的话;我手边没有它的副本可供参考。

该语言不允许执行您想要的操作。CCD_ 15和CCD_。我想不出!(x==y)x!=y会产生不同结果的例子,但考虑一下operator<=operator>。为什么你需要这两个?可以把x<=y写成!(x>y),对吧?错误的

#include<iostream>
int main () {
double y = 0.0;
double x = y/y;
std::cout << " (x <= y) -> " << (x <= y) << "n";
std::cout << "!(x >  y) -> " << !(x > y) << "n";
}

C++的第一个版本(更正后的C++03)引入了默认构造函数、复制构造函数、复制赋值运算符和析构函数的自动定义,以便能够在C++中编译C。

事实证明,它可能不是最好的选择,但许多为析构函数提供自定义定义的人忘记了定义复制构造函数和赋值运算符,结果弄得一团糟。

隐式方法,比如隐藏的执行路径,似乎会让开发人员感到困惑。我想我们都被咬了。


C++11有一个非常聪明的按需默认方法机制:

class Test { Test() = default; }; // a rather useless class...

因此,借鉴C++03中构造函数的自动生成&co,我不赞成引入这一代汽车,但肯定会支持:

bool operator!=(Test const&, Test const&) = default;

(显然operator==在范围内)

类似地:

bool operator>(Test const&, Test const&) = default;
bool operator<=(Test const&, Test const&) = default;
bool operator>=(Test const&, Test const&) = default;

(显然operator<在范围内)


然而,我们可能会问一个真正的问题:为什么不提供一个非常通用的方法?

operator==通常不会搞砸,但我见过无数operator<的坏实现。显然,尊重弱势秩序并不像看上去那么容易(*)。尽管如此,如果你认为元组,它只是两个元组的字典比较,真的!

(*)您有==<的实现不匹配(即通常为!(a < b) and !(b < a)<=>a == b),但元组确实解决了这一问题

实际上:

std::tuple<int, std::string const&> to_tuple(Test const&);

通常可以用于生成初始运算符:

template <typename T>
auto operator==(T const& left, T const& right) -> decltype(to_tuple(left), bool{}) {
return to_tuple(left) == to_tuple(right);
}
template <typename T>
auto operator<(T const& left, T const& right) -> delctype(to_tuple(right), bool{}) {
return to_tuple(left) < to_tuple(right);
}

那么,陷阱是什么呢?嗯,ADL。当这些模板位于与您实现to_tuple的类不同的命名空间中时,事情就会分崩离析,因为它们不会被ADL自动拾取(using std::swap非常常见也是同样的原因…)

因此,如果to_tuple(Test const&)在范围内,我们可以认为bool operator==(Test const&, Test const&) = default;应该做正确的事情(tm)。这甚至不会是疯狂的。不太多。

然而,看看我离最初的提议有多远?想象一下,委员会的决定最终会是什么。。。


在平均时间内?

好吧,就我个人而言,我实现了:

#define MY_DEFINE_TUPLE_OPERATOR_IMPL(Type_, Op_)                       
inline bool operator Op_ (Type_ const& left, Type_ const& right) {  
return to_tuple(left) Op_ to_tuple(right);                      
}                                                                   
#define MY_DEFINE_TUPLE_EQUAL(Type_)                                    
MY_DEFINE_TUPLE_OPERATOR_IMPL(Type_, ==)                            
MY_DEFINE_TUPLE_OPERATOR_IMPL(Type_, !=)
#define MY_DEFINE_TUPLE_COMP(Type_)                                     
MY_DEFINE_TUPLE_OPERATOR_IMPL(Type_,  <)                            
MY_DEFINE_TUPLE_OPERATOR_IMPL(Type_,  >)                            
MY_DEFINE_TUPLE_OPERATOR_IMPL(Type_, <=)                            
MY_DEFINE_TUPLE_OPERATOR_IMPL(Type_, >=)

然后:

class Test;
std::tuple<int, std::string const&> to_tuple(Test const&); // or boost::tuple
MY_DEFINE_TUPLE_EQUAL(Test);
MY_DEFINE_TUPLE_COMP(Test);

它与ADL一起工作,它围绕to_tuple生成内联代码(它本身可能是内联的,也可能不是内联的),它生成正确一致的==<实现,并且对于所有6种方法,它的类型都比= default少。

甚至为编译器错误消息留下了源位置!

所以。。。为什么要让语言更加复杂呢?