Почему нельзя перегрузить троичный оператор?

Почему невозможно перегрузить тернарный оператор ?: '?

Я часто использую тернарный оператор для консолидации операторов if, и мне любопытно, почему разработчики языка решили запретить этому оператору перегружаться. Я искал объяснения, почему в Перегрузка Оператора C++ но не нашел ни одного описания, почему это невозможно. Единственная информация, содержащаяся в сноске, заключается в том, что ее нельзя перегружать.

мой первоначальный предположим, что перегрузка оператора почти всегда будет нарушать номер один или два из принципов, приведенных в ссылке выше. Значение перегрузки редко будет очевидным или ясным, или оно будет отклоняться от своей первоначальной известной семантики.

поэтому мой вопрос скорее в том, почему это невозможно, чем как, Поскольку я знаю, что это невозможно.

5 ответов


Я думаю, что главная причина в то время, что это не стоило попытка изобрести новый синтаксис только для этого оператора. Нет никакого знака ?:, поэтому вам придется создать несколько специальные грамматические правила как раз для этого. (Текущее правило грамматики имеет operator за ним следует оператор, который является единственным знак.)

как мы узнали (из опыта), чтобы использовать перегрузку оператора более разумно, стало очевидно, что мы действительно не должны допустили перегрузку && и || либо, для причины, на которые указывают другие ответы, и, вероятно, нет оператор "запятая" (с перегруженных версий не будет точка последовательности, которую ожидает пользователь). Итак, мотивация поддерживать его еще меньше, чем было изначально.


если бы вы могли переопределить тернарный оператор, вам пришлось бы написать что-то вроде этого:

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

чтобы вызвать переопределение, компилятор должен был бы вычислить значение обоих trueVal и falseVal. Встроенный тернарный оператор работает не так - он вычисляет только одно из этих значений, поэтому вы можете писать такие вещи, как:

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

, не беспокоясь о indirecting через нулевой указатель.


один из принципов троичного оператора заключается в том, что истинное / ложное выражение оценивается только на основе истинности или ложности условного выражения.

cond ? expr1 : expr2
expr1 вычисляется только если cond имеет значение true, если expr2 вычисляется только если cond ложно. Имея это в виду, давайте посмотрим, как будет выглядеть подпись для троичной перегрузки (используя фиксированные типы здесь вместо шаблона для простота)
Result operator?(const Result& left, const Result& right) { 
  ...
}

эта подпись просто не является законной, потому что она нарушает точную семантику, которую я описал. Чтобы вызвать этот метод, язык должен был бы оценить оба expr1 и expr2 следовательно, они больше не оцениваются условно. Для поддержки тернарного оператора необходимо либо

  1. возьмите лямбда для каждого значения, чтобы он мог производить их по требованию. Это обязательно усложнит вызывающий код, хотя, потому что это пришлось бы учитывать семантику лямбда-вызова, где лямбда логически не присутствовала
  2. тернарный оператор должен будет вернуть значение, чтобы указать, должен ли компилятор использовать expr1 или expr2

редактировать

некоторые могут возразить, что отсутствие короткого замыкания в этом случае нормально. Причина в том, что C++ уже позволяет нарушать короткое замыкание при перегрузках оператора с || и &&

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

хотя я все еще нахожу это поведение озадачивающим даже для C++.


по той же причине, почему вы действительно не должны (хотя вы можете) перегрузка && или || операторы - это отключит короткое замыкание на этих операторах (оценивая только необходимую часть, а не все), что может привести к серьезным осложнениям.


короткий и точный ответ просто "потому что это то, что решил Бьярне."

хотя аргументы о том, какие операнды следует оценивать и в какой последовательности дают технически точное описание того, что происходит, они мало (ничего, на самом деле) объясняют, почему этот конкретный оператор не может быть перегружен.

в частности, те же основные аргументы будут одинаково хорошо применяться к другим операторам, таким как operator && и operator||. В встроенном версия каждого из этих операторов, левый операнд вычисляется, если и только если это производит 1 на && или 0 на ||, вычисляется правый операнд. Аналогично, оператор запятой (встроенный) вычисляет свой левый операнд, а затем его правый операнд.

в перегруженной версии любого из этих операторов и операнда всегда вычисляются (в произвольном порядке). Таким образом, они по существу идентичны перегруженной тройке оператор в этом отношении. Все они теряют одинаковые гарантии о том, какие операнды оцениваются и в каком порядке.

, почему он принял такое решение: я вижу несколько возможностей. Во-первых, хотя технически это оператор, троичный оператор посвящен в первую очередь управлению потоком, поэтому перегрузка будет больше похожа на перегрузку if или while чем это похоже на перегрузку большинства других операторов.

другой возможностью было бы то, что это будет синтаксически некрасиво, требуя от парсера иметь дело с чем-то вроде operator?:, что требует определения ?: в качестве токена и т. д. -- все это требует достаточно серьезных изменений в грамматике. По крайней мере, на мой взгляд, этот аргумент кажется довольно слабым, так как C++ уже требует много более сложный парсер, чем C, и это изменение будет действительно много меньше, чем многие другие изменения, которые были сделаны.

возможно, самый сильный аргумент из всех просто что, похоже, это ничего не даст. Поскольку он посвящен в первую очередь управлению потоком, изменение того, что он делает для некоторых типов операндов, вряд ли приведет к чему-то очень полезному.