为什么不能重载三元运算符'?:'?
我经常使用三元运算符来合并 if 语句,并且很好奇为什么语言设计者选择禁止该运算符重载。我在C++ 运算符重载中寻找解释,但没有找到描述为什么这是不可能的解释。脚注提供的唯一信息是它不能被重载。
我最初的猜测是,重载运算符几乎总是会违反上面链接中给出的第一或第二个原则。重载的含义很少会很明显或清楚,或者会偏离其原始已知语义。
所以我的问题更多是为什么这是不可能的,而不是如何,因为我知道这是不可能的。
为什么不能重载三元运算符'?:'?
我经常使用三元运算符来合并 if 语句,并且很好奇为什么语言设计者选择禁止该运算符重载。我在C++ 运算符重载中寻找解释,但没有找到描述为什么这是不可能的解释。脚注提供的唯一信息是它不能被重载。
我最初的猜测是,重载运算符几乎总是会违反上面链接中给出的第一或第二个原则。重载的含义很少会很明显或清楚,或者会偏离其原始已知语义。
所以我的问题更多是为什么这是不可能的,而不是如何,因为我知道这是不可能的。
如果您可以覆盖三元运算符,则必须编写如下内容:
xxx operator ?: ( bool condition, xxx trueVal, xxx falseVal );
要调用您的覆盖,编译器必须计算 和 的trueVal
值falseVal
。这不是内置三元运算符的工作方式 - 它只计算其中一个值,这就是为什么您可以编写如下内容:
return p == NULL ? 23 : p->value;
无需担心通过 NULL 指针进行间接寻址。
我认为当时的主要原因是,为该运算符发明一种新语法似乎并不值得。没有 token ?:
,所以你必须为它创建一些特殊的语法规则。(当前的语法规则operator
后面有一个运算符,它是一个单一的标记。)
正如我们(从经验中)学到的更合理地使用运算符重载,很明显我们真的不应该允许重载&&
and||
因为重载版本没有用户期望的序列点)。所以支持它的动力比原来还要少。
三元运算符的原理之一是仅根据条件表达式的真假来评估真/假表达式。
cond ? expr1 : expr2
在此示例expr1
中,仅在cond
为真时expr2
评估,而仅在cond
为假时评估。记住这一点,让我们看看三元重载的签名是什么样的(为了简单起见,这里使用固定类型而不是模板)
Result operator?(const Result& left, const Result& right) {
...
}
这个签名根本不合法,因为它违反了我描述的确切语义。为了调用此方法,语言必须同时评估两者expr1
,expr2
因此不再有条件地评估它们。为了支持三元,运营商要么需要
expr1
或expr2
编辑
有些人可能会争辩说,在这种情况下没有短路是好的。||
原因是 C++ 已经允许您在运算符重载中使用and来违反短路&&
Result operator&&(const Result& left, const Result& right) {
...
}
尽管即使对于 C++,我仍然觉得这种行为令人费解。
简短而准确的答案只是“因为这是 Bjarne 的决定。”
尽管关于应该评估哪些操作数以及以什么顺序进行评估的论点给出了技术上准确的描述,但它们几乎没有(实际上没有)解释为什么不能重载这个特定的运算符。
特别是,相同的基本参数同样适用于其他运算符,例如operator &&
and operator||
。在这些运算符中的每一个的内置版本中,都会计算左操作数,然后当且仅当产生1
for&&
或0
for||
时,才会计算右操作数。同样,(内置)逗号运算符先计算其左操作数,然后计算其右操作数。
在任何这些运算符的重载版本中,总是对两个操作数进行评估(以未指定的顺序)。因此,在这方面,它们本质上与重载的三元运算符相同。它们都失去了关于评估哪些操作数以及以什么顺序进行评估的相同保证。
至于为什么 Bjarne 做出这个决定:我可以看到一些可能性。一是虽然它在技术上是一个运算符,但三元运算符主要用于流控制,因此重载它更像是重载if
,或者while
不像重载大多数其他运算符。
另一种可能性是它在语法上很丑陋,需要解析器处理类似的东西operator?:
,这需要定义?:
为标记等——所有这些都需要对 C 语法进行相当严重的更改。至少在我看来,这个论点似乎很弱,因为 C++ 已经需要一个比 C 复杂得多的解析器,而且这种变化确实比已经做出的许多其他变化要小得多。
也许最有力的论据只是它似乎不会取得太大成就。由于它主要用于流控制,因此更改它对某些类型的操作数的作用不太可能完成任何非常有用的事情。
出于同样的原因,您确实不应该(尽管您可以)重载&&
或||
运算符 - 这样做会禁用这些运算符的短路(仅评估必要的部分而不是所有内容),这可能会导致严重的并发症。