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

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

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

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

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

Ответ 1

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

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

Ответ 2

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

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

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

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

не беспокоясь о косвенности с помощью указателя NULL.

Ответ 3

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

cond ? expr1 : expr2

В этом примере expr1 оценивается только в том случае, если cond является истинным, а expr2 оценивается только в том случае, если cond - false. Помня об этом, давайте посмотрим, как будет выглядеть подпись для тройной перегрузки (для упрощения используйте здесь фиксированные типы вместо шаблона)

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

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

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

ИЗМЕНИТЬ

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

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

Хотя я по-прежнему считаю это поведение недоумением даже для С++.

Ответ 4

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

Ответ 5

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

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

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

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

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

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

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