为啥不能重载三元运算符?

Posted

技术标签:

【中文标题】为啥不能重载三元运算符?【英文标题】:Why is it not possible to overload the ternary operator?为什么不能重载三元运算符? 【发布时间】:2013-07-30 23:28:57 【问题描述】:

为什么不能重载三元运算符'?:'?

我经常使用三元运算符来合并 if 语句,并且很好奇为什么语言设计者选择禁止该运算符被重载。我在C++ Operator Overloading 中寻找解释原因,但没有找到描述为什么这是不可能的解释。脚注提供的唯一信息是它不能被重载。

我最初的猜测是,重载运算符几乎总是会违反上面链接中给出的第一或第二个原则。重载的含义很少会很明显或清楚,或者会偏离其原始已知语义。

所以我的问题更多是为什么这是不可能的,而不是如何,因为我知道这是不可能的。

【问题讨论】:

在三元运算符中重载可能被解释为“评估”的所有内容的能力是否不够? How to overload the conditional operator?的可能重复 您究竟想让它重载做什么?这是一个if-else 声明,你怎么能以任何有意义的方式改变它? 感谢 cmets 和链接。我确实看到链接的问题可能重复,但没有找到任何回答我的问题的答案。 【参考方案1】:

如果您可以覆盖三元运算符,则必须编写如下内容:

xxx operator ?: ( bool condition, xxx trueVal, xxx falseVal );

要调用您的覆盖,编译器必须计算trueValfalseVal 的值。这不是内置三元运算符的工作方式 - 它只计算其中一个值,这就是为什么您可以编写如下内容:

return p == NULL ? 23 : p->value;

不用担心通过 NULL 指针进行间接寻址。

【讨论】:

&&|| 的推理相同,您可以超载。 @JamesKanze,这是一个多么可怕的决定 @JamesKanze 当然。后见之明是一件美妙的事情。如果 C++11 默认禁用运算符 &&||, 的重载,我个人会喜欢它,除非程序员包含一些特殊的标头(或传递一个特殊的编译器开关)。我不认为一种语言允许过去的错误在向后兼容的祭坛上永久传播是不明智的。 @NikBougalis:向后兼容性是一个巨大的负担。有些编译器会因为他们的客户使用那些不兼容的功能而使他们与标准不兼容。这只是其中一种情况(即禁止重载||,您会发现供应商维护支持作为扩展,因为他们的客户正在使用它——无论他们是否应该使用它,毕竟这是他们的钱.. .) @NikBougalis 标准的作用并不是促进良好实践(事实上,在许多情况下,它的作用恰恰相反);它是为了规范现有的做法。今天没有什么可以阻止编译器警告这种做法。 (但它不是非黑即白。标准可能会弃用它们,例如鼓励编译器警告。)【参考方案2】:

出于同样的原因,您确实不应该(尽管您可以)重载 &&|| 运算符 - 这样做会禁用这些运算符的短路(仅评估必要的部分而不是所有内容),这可以导致严重的并发症。

【讨论】:

【参考方案3】:

三元运算符的一个原则是,真/假表达式只根据条件表达式的真假进行评估。

cond ? expr1 : expr2

在此示例中,expr1 仅在 cond 为真时评估,而 expr2 仅在 cond 为假时评估。记住这一点,让我们看看三元重载的签名是什么样的(为了简单起见,这里使用固定类型而不是模板)

Result operator?(const Result& left, const Result& right)  
  ...

这个签名根本不合法,因为它违反了我描述的确切语义。为了调用此方法,语言必须同时评估expr1expr2,因此不再有条件地评估它们。为了支持三元,操作员要么需要

    为每个值取一个 lambda,以便它可以按需生成它们。不过,这必然会使调用代码复杂化,因为它必须考虑逻辑上不存在 lambda 的 lambda 调用语义 三元运算符需要返回一个值来表示编译器应该使用expr1 还是expr2

编辑

有些人可能会争辩说,在这种情况下没有短路是可以的。原因是 C++ 已经允许您在 ||&& 的运算符重载中违反短路

Result operator&&(const Result& left, const Result& right)  
  ...

虽然我仍然觉得这种行为对于 C++ 来说是莫名其妙的。

【讨论】:

【参考方案4】:

我认为当时它似乎不值得的主要原因 为该运算符发明新语法的努力。 没有令牌?:,所以你必须创建一些 专门的语法规则。 (当前的语法规则 有operator 后跟一个运算符,这是一个单一的 令牌。)

正如我们(从经验中)学到的那样使用运算符重载 更合理的是,很明显我们真的不应该 已经允许&&|| 的重载,对于 其他回应指出的原因,可能不是 运算符逗号也是如此(因为重载版本不会有 用户期望的序列点)。所以动机 支持它甚至比原来的还要少。

【讨论】:

'?'和 ':' 是分开的,但语法中的产生式规则是作为一个整体处理的。我过去在 yacc 中实现了这个运算符,使用如下代码:expr : expr '?' expr ':' expr $$ = new ConditionalOp($1, $3, $5); ;【参考方案5】:

简短而准确的答案是“因为这是 Bjarne 的决定。”

尽管关于应该评估哪些操作数以及以什么顺序计算的参数给出了技术上准确的描述,但它们几乎没有(实际上没有)解释为什么不能重载这个特定的运算符。

特别是,相同的基本参数同样适用于其他运算符,例如operator &&operator||。在这些运算符中的每一个的内置版本中,都会计算左操作数,然后当且仅当它为&& 生成1 或为|| 生成0 时,才计算右操作数。同样,(内置)逗号运算符先计算左操作数,然后计算右操作数。

在任何这些运算符的重载版本中,两个操作数总是被计算(以未指定的顺序)。因此,在这方面,它们本质上与重载的三元运算符相同。它们都失去了关于评估哪些操作数以及以什么顺序进行评估的相同保证。

关于 Bjarne 做出这个决定的原因:我可以看到一些可能性。一是虽然它在技术上是一个运算符,但三元运算符主要用于流控制,因此重载它更像是重载ifwhile,而不是重载大多数其他运算符。

另一种可能性是它在语法上很丑陋,需要解析器处理类似operator?: 的东西,这需要将?: 定义为标记等——所有这些都需要对C 语法进行相当严重的更改。至少在我看来,这个论点似乎很弱,因为 C++ 已经需要一个比 C 复杂得多的解析器 ,而且这种变化确实比许多其他的要小很多所做的更改。

也许最有力的论点只是它似乎不会有太大的成就。由于它主要用于流控制,因此更改它对某些类型的操作数的作用不太可能完成任何非常有用的事情。

【讨论】:

当时真正的论点是语法要求重载函数命名为“运算符 ”,其中 是单个标记。您需要额外的语法规则来处理这个标记。 @JamesKanze:我正在编辑以涵盖这一点。否则,解析 C++ 的复杂性似乎(在我看来)充其量只能使这个论点变得很弱。将?: 定义为令牌(即使它只在一个地方使用)并不完全是火箭科学。如您所知,解析 C++ 的其他部分要困难得多 相当。然而,这就是我被有能力知道的人给我的原因。但我认为这是解析器问题结合的感觉,它不会买太多。如果您没有得到任何回报,那么添加任何额外的复杂性都是没有意义的。 &&|| 是“免费的”,所以禁止它们是没有意义的,因为那时还没有意识到它们会有多么无用。

以上是关于为啥不能重载三元运算符?的主要内容,如果未能解决你的问题,请参考以下文章

三元运算符中为啥不能使用braced-init-list?

jquery 直接在CSS函数中设置三元运算符

三元运算符为啥以及何时返回左值?

为啥 Go 没有三元条件运算符 [关闭]

为啥我的三元运算符会出现这些错误?

为啥 Kotlin 不支持“三元运算符”[关闭]