为什么 C++ 运算符重载要求“至少有一个参数是类类型”?

15

《C++ Primer 第五版》的第14.1章写道:

一个运算符函数必须是类的成员或至少有一个参数是类类型。

例如,string("hello")+"world" 是可编译的,而 "hello"+"world" 则不能编译。当我想要重载两个 C 字符串之间的 + 运算符时也是同样的情况。

std::string operator+ (const char* s1, const char* s2)

我遇到了以下错误。

错误:'std::string operator+(const char*, const char*)'必须具有类或枚举类型的参数

我有两个问题。

  1. 这种限制是语言规范的一部分吗?如果是,为什么 C++ 设计者要这样做?

  2. std::string 有像 string (const char* s); 这样的构造函数,这意味着编译器可以将 char* 隐式转换为 string 类型。当我们调用 "hello" + "world" 时,为什么编译器不会将这两个 char* 转换为两个字符串呢? 毕竟,在两个 std::strings 上有一个重载的 "+"。 string operator+ (const string& lhs, const string& rhs);


11
如果您可以重载两个指针的加法,那将会产生一些“有趣”的指针算术。 - Anycorn
@Anycorn 太棒了。在我定义了string_add(std::string s1, std::string s2)之后,我可以毫无问题地调用"string_add("hello", "world")"。因此,似乎只要函数不是运算符重载,就存在隐式转换。 - Peng Zhang
3
这是因为std::string可以从char*构造。操作顺序是,先用char*构造s1,再用char*构造s2,然后将这些临时变量传递给您的函数。 - W.B.
@anthony-arnold 我认为你的评论与Anycorn一样好。这种函数定义会让编译器混淆,因为在C++中指针是整数。 - Peng Zhang
1
我认为只要一个类型不是内置的,比如枚举类型,就足够了。 - Kerrek SB
显示剩余2条评论
3个回答

11
这个限制是语言规范的一部分吗?
是的,它是。为什么?主要原因可能是因为重新定义标准类型的通常运算符被认为是模糊的。 比如重载operator+(int,int)或者operator+(int,char*)会改变现有代码的含义!
你可以说有一些未定义的标准类型之间的运算符,因此您可以安全地覆盖它们,例如operator*(char*,int),但是通常被认为是相当无用的。
此外,操作符重载必须是全局的(或者在其成员的命名空间中,但是对于标准类型来说,没有可依赖的命名空间),因此喜欢覆盖它们的库之间的互操作性将会是一场噩梦。
“hello”+“world”:为什么编译器不把两个char*转换为两个字符串?
首先,std::operator+(const std::string&, const std::string&)namespace std;中,因此默认情况下不会被找到。
你可以尝试显式调用此操作符:std::operator+(“hello”,“world”),但遗憾的是,有太多的operator+()重载,其中许多是模板,该调用是有歧义的。
因此,综合考虑,我认为合理的要求是至少有一个运算符是用户定义的类型。想一想:全局和命名空间问题得到了解决,可以使用ADL(实际上它是为此而发明的),并且不可能重新定义具有现有含义的运算符(除了operator,()operator&()之外,但是谁想覆盖这些...)。

1
一个类类型的operator=已经存在了含义。 - aschepler
@aschepler:哦,你说得对!虽然在这种情况下,重载不是玄学的,而是需要保证类不变量。 - rodrigo
“通常被认为是相当无用的” 阅读此问题:https://stackoverflow.com/q/5703271/126995 - Soonts

8

正如Bjarne在《设计与演化》中所说,目标是使语言易于扩展,而不是变异。如果只允许重载内置类型,那么就会改变语言,并且允许这样做会违背设计目标。(例如,它会鼓励形成相互不兼容的方言并破坏社区。)


据我所知,这是一个避免允许重载已定义语义的原因,例如 T* operator+(T*, ptrdiff_t) 和其对应的 T* operator+(ptrdiff_t, T*),但我怀疑它是否是禁止重载先前未定义运算符的好理由,如 string operator+(const char*, const char*) - Massa
3
@Massa:这也许是展示 C++ 设计如何解决您问题的一个好例子:您可以写 "abc"s + "def"s 来得到一个字符串。通过巧妙地添加新功能(用户自定义字面量),而非改变现有语义,提供了一个简洁的解决方案。 - Kerrek SB
@KerrekSB,我还在等待"abc"sv和相应的string_view类呢:p - chris
@Massa,你肯定是指libstdc++。你仍然可以使用带有libstdc++(或libc++)的Clang。看起来libc++正在开发中。 - chris
1
@Massa:目前还没有最终确定,因此当前实现中的不一致和错误并不令人惊讶。给它一些时间(或在Clang Bug跟踪器上提交错误报告?)。 - Kerrek SB
显示剩余3条评论

6
这个限制是语言规范的一部分吗?如果是,为什么C++设计者要这样做?
是的,它是。引用自N3337,§13.5.6 [over.oper]:
运算符函数必须是非静态成员函数或非成员函数,并且至少有一个参数的类型是类、类的引用、枚举或枚举的引用。
关于“为什么”,因为去除此要求意味着你可以为内置类型重载运算符,而这些类型已经定义了它们的运算符语义。这是完全不可取的。
例如,你认为定义这个有任何意义吗?
int operator+(int a, int b) { return a - b; }

当其他人阅读您的代码时,它是否能让更多人理解您的程序,或者只是令人惊讶的东西,有效地破坏了您的推理?

如果我们在游戏中获得指针会发生什么?

bool operator==(const char *str1, const char *str2) { 
    return strcmp(str1, str2) == 0;
}

你会想到 operator== 现在会解引用内存吗?我不会。这很令人惊讶。它违反了标准对这些运算符的规定。它打破了过去 25 年中每个 C 程序的行为。语言不应该让你做这种滥用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接