Why is it not possible to overload the ternary operator?
Asked Answered
F

6

26

Why is it not possible to overload the ternary operator ' ?: '?

I use the ternary operator often to consolidate if statements, and am curious why the language designers chose to forbid this operator from being overloaded. I looked for an explanation as to why in C++ Operator Overloading but did not find one describing why this isn't possible. The only information the footnote provides is that it cannot be overloaded.

My initial guess is that overloading the operator will almost always violate number one or two of the principles given in the link above. The meaning of the overload will rarely be obvious or clear or it will deviate from its original known semantics.

So my question is more of why is this not possible rather than how, as I know it cannot be done.

Faunia answered 30/7, 2013 at 17:23 Comment(4)
Is the ability to overload everything that could be construed as "evaluatory" within a ternary operator not enough?Cletacleti
possible duplicate of How to overload the conditional operator?Cashbox
Exactly what would you like to overload it to do? It is an if-else statement, how can you change it in any meaningful way?Brassica
Thanks for the comments and link. I did see the the question linked as a potential duplicate, but did not find any responses there that answered my question.Faunia
H
18

I think the main reason at the time that it didn't seem worth the effort of inventing a new syntax just for that operator. There is no token ?:, so you'd have to create a number of special grammar rules just for it. (The current grammar rule has operator followed by an operator, which is a single token.)

As we've learned (from experience) to use operator overloading more reasonably, it has become apparent that we really shouldn't have allowed overloading of && and || either, for the reasons other responses have pointed out, and probably not operator comma as well (since the overloaded versions won't have the sequence point which the user expects). So the motivation to support it is even less than it was originally.

Highfalutin answered 30/7, 2013 at 17:34 Comment(1)
'?' and ':' are separated, but production rule in grammar is processed as a whole. I implemented this operator in yacc in the past, using code like this: expr : expr '?' expr ':' expr { $$ = new ConditionalOp($1, $3, $5); };Christoforo
C
25

if you could override the ternary operator, you would have to write something like this:

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

To call your override, the compiler would have to calculate the value of both trueVal and falseVal. That's not how the built-in ternary operator works - it only calculates one of those values, which is why you can write things like:

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

without worrying about indirecting through a NULL pointer.

Checked answered 30/7, 2013 at 17:26 Comment(8)
The same reasoning holds for && and ||, which you can overload.Highfalutin
@JamesKanze and what a horrible decision that was.Bicapsular
@NikBougalis At the time, no one had any real experience with overloading, so we didn't know. And now, we can't get rid of it because of backwards compatibility. (Of course, nothing stops a compiler from issuing a warning if you do try it.)Highfalutin
@JamesKanze Of course. Hindsight is a wonderful thing. I'd have personally liked it if C++11 disabled the overloading of operator &&, || and , by default, unless the programmer included some special header (or passed a special compiler switch). I don't think it's sane for a language to allow past mistakes to propagate forward perpetually on the altar of backwards compatibility.Bicapsular
@NikBougalis: Backwards compatibility is a huge load to carry. There are compilers that perpetuate their incompatibilities with the standard just because their clients use those incompatible features. This would be just one more of those cases (i.e. forbid overloading || and you will find that vendors maintain support as an extension because their clients are using it --whether they should or not use it, it's their money after all...)Sideway
@DavidRodríguez-dribeas It is. Which is why, at some point, we should shed it. I don't think we need to wait for (C++)++ for that to happen. And, yes, if compiler vendors want to support things as an extension, they can. Frankly, that's better than continuing to burden the core, standard language with bad ideas.Bicapsular
@NikBougalis The role of the standard isn't to promote good practice (and in fact, in many cases, it does just the opposite); it is to standardize existing practice. And there's nothing today which would prevent compilers from warning about the practice. (But it's not black and white. The standard could deprecate them, to encourage compiler warnings, for example.)Highfalutin
I would think the interface would be xxx operator ?: ( bool condition, xxx trueVal, something_callable<xxx> falseVal ); and then you would could implement it as { if (condition) return trueVal; return falseVal(); }.Neurogenic
H
18

I think the main reason at the time that it didn't seem worth the effort of inventing a new syntax just for that operator. There is no token ?:, so you'd have to create a number of special grammar rules just for it. (The current grammar rule has operator followed by an operator, which is a single token.)

As we've learned (from experience) to use operator overloading more reasonably, it has become apparent that we really shouldn't have allowed overloading of && and || either, for the reasons other responses have pointed out, and probably not operator comma as well (since the overloaded versions won't have the sequence point which the user expects). So the motivation to support it is even less than it was originally.

Highfalutin answered 30/7, 2013 at 17:34 Comment(1)
'?' and ':' are separated, but production rule in grammar is processed as a whole. I implemented this operator in yacc in the past, using code like this: expr : expr '?' expr ':' expr { $$ = new ConditionalOp($1, $3, $5); };Christoforo
S
9

One of the principles of the ternary operator is that the true / false expression are only evaluated based on the truth or falseness of the conditional expression.

cond ? expr1 : expr2

In this example expr1 is only evaluated if cond is true while expr2 is only evaluated if cond is false. Keeping that in mind lets look at what a signature for ternary overloading would look like (using fixed types here instead of a template for simplicity)

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

This signature simply isn't legal because it violates the exact semantics I described. In order to call this method the language would have to evaluate both expr1 and expr2 hence they are no longer conditionally evaluated. In order to support ternary the operator would either need to

  1. Take a lambda for each value so it could produce them on demand. This would necessarily complicate the calling code though because it would have to take into account lambda call semantics where no lambda was logically present
  2. The ternary operator would need to return a value to denote whether the compiler should use expr1 or expr2

EDIT

Some may argue that the lack of short circuiting in this scenario is fine. The reason being that C++ already allows you to violate short circuiting in operator overloads with || and &&

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

Though I still find this behavior baffling even for C++.

Styrax answered 30/7, 2013 at 17:31 Comment(0)
I
4

The short and accurate answer is simply "because that's what Bjarne decided."

Although the arguments about which operands should be evaluated and in what sequence give a technically accurate description of what happens, they do little (nothing, really) to explain why this particular operator can't be overloaded.

In particular, the same basic arguments would apply equally well to other operators such as operator && and operator||. In the built-in version of each of these operators, the left operand is evaluated, then if and only if that produces 1 for && or a 0 for ||, the right operand is evaluated. Likewise, the (built in) comma operator evaluates its left operand, then its right operand.

In an overloaded version of any of these operators, both operands are always evaluated (in an unspecified sequence). As such, they're essentially identical to an overloaded ternary operator in this respect. They all lose the same guarantees about what operands are evaluated and in what order.

As to why Bjarne made that decision: I can see a few possibilities. One is that although it's technically an operator, the ternary operator is devoted primarily to flow control, so overloading it would be more like overloading if or while than it is like overloading most other operators.

Another possibility would be that it would be syntactically ugly, requiring the parser to deal with something like operator?:, which requires defining ?: as a token, etc. -- all requiring fairly serious changes to the C grammar. At least in my view, this argument seems pretty weak, as C++ already requires a much more complex parser than C does, and this change would really be much smaller than many other changes that have been made.

Perhaps the strongest argument of all is simply that it didn't seem like it would accomplish much. Since it is devoted primarily to flow control, changing what it does for some types of operands is unlikely to accomplish anything very useful.

Inclose answered 30/7, 2013 at 17:46 Comment(3)
The real argument, back then, was that the grammar required the overloading function to be named "operator <op>", where <op> was a single token. You'd have needed additional grammar rules just for this token.Highfalutin
@JamesKanze: I was editing to cover that. The complexity of parsing C++ otherwise seems (to me) to render this argument pretty weak at best. Defining ?: as a token (even though it's only used in one place) isn't exactly rocket science. As you're well aware, other parts of parsing C++ are much more difficult.Inclose
Quite. That is, however, the reason I was given, by people who were in a position to know. But I think it was the parser issue combined with the feeling that it wouldn't buy much. No point in adding the slightest additional complexity if you don't get anything in return. Where as && and || were "free", so there was no point in banning them, since it wasn't realized then how useless they would be.Highfalutin
E
0

For the same reason why you really should not (although you can) overload && or || operators - doing so would disable short-circuiting on those operators (evaluating only the necessary part and not everything), which can lead to severe complications.

Excusatory answered 30/7, 2013 at 17:28 Comment(0)
C
0

Previous answers focused on short-circuiting, which is somewhat valid but not even the real problem with trying to do this IMO.

The closest possible implementation of the existing ternary operator (without short circuiting) would have to look like this:

template<typename T0, typename T1>
std::variant<T0, T1>&& operator?:(bool predicate, T0&& arg0, T1&& arg1)
{
    if(predicate)
        return { std::forward<T0&&>(arg0) };
    return { std::forward<T1&&>(arg1); }
}

However, T0 might be void. T1 might be void. This won't build in either of those cases.

The variant is necessary because T0 and T1 might not be implicitly convertible to one another and the return type can't be used for function overload resolution, and that was a C++17 library addition. But it still doesn't really work, because variant isn't implicitly convertible to any of its possible types.

Cold answered 18/5, 2022 at 6:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.