Оптимизация закона Де Моргана с перегруженными операторами

Каждый программист должен знать, что:

De Morgan 1
De Morgan 2
(Законы Моргана)

В некоторых случаях, чтобы оптимизировать программу, может случиться, что компилятор изменяет (!p && !q) на (!(p || q)).

Два выражения эквивалентны, и нет никакой разницы в оценке первой или второй.
Но в С++ можно перегрузить операторы, и перегруженный оператор может не всегда уважать это свойство. Таким образом, преобразование кода таким образом фактически изменит код.

Должен ли компилятор использовать законы Де Моргана, когда !, || и && перегружены?

Ответ 1

Обратите внимание, что:

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

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

Источник: http://en.cppreference.com/w/cpp/language/operator_logical(акцент мой)

И что:

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

Источник: n4431 13.6 Встроенные операторы [over.built] (выделено мной)

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

НЕТ, компилятор не заменит вызов пользовательской функции вызовом другой пользовательской функции. В противном случае потенциально может нарушить правило "как будто" .

Ответ 2

Я думаю, что вы ответили на свой вопрос: нет, компилятор не может этого сделать. Не только операторы могут быть перегружены, некоторые не могут быть даже определены. Например, вы можете определить operator && и operator !, а operator || не определен вообще.

Обратите внимание, что есть много других законов, которые компилятор не может выполнить. Например, он не может изменить p||q на q||p, а также x+y на y+x.

(Все вышеперечисленное относится к перегруженным операторам, так как это задает вопрос.)

Ответ 3

Нет, в этом случае преобразование будет недействительным. Разрешение преобразовать !p && !q в !(p || q) неявно, по правилу as-if. Правило as-if допускает любое преобразование, которое, грубо говоря, не может быть соблюдено с помощью правильной программы. Когда используются перегруженные операторы и обнаруживают преобразование, это автоматически означает, что преобразование больше не разрешено.

Ответ 4

Перегруженные операторы сами по себе являются просто синтаксическим сахаром для вызовов функций; самому компилятору не разрешается делать какие-либо предположения о свойствах, которые могут или не могут выполняться для таких вызовов. Оптимизации, которые используют свойства некоторого конкретного оператора (например, Де Моргана для булевых операторов, коммутативность для сумм, дистрибутивность для суммы/произведения, преобразование интегрального деления в соответствующем умножении,...) могут быть использованы только тогда, когда "действительные операторы" используются.

Обратите внимание, что некоторые части стандартной библиотеки могут связывать некоторые специфические смысловые значения с перегруженными операторами - например, std::sort по умолчанию ожидает, что operator< соответствует строгому слабому упорядочению между элементами - но это относится к курс, указанный в предпосылках каждого алгоритма/контейнера.

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

Ответ 5

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

Ответ: конечно нет!

  • Если применяются законы Де Моргана, они могут применяться.
  • Если они этого не делают, они не могут.

Это действительно так просто.

Ответ 6

Не напрямую.

Если p и q являются выражениями, так что p не имеет перегруженных операторов, выполняется оценка короткого замыкания: выражение q будет оцениваться только в том случае, если p является ложным.

Если p имеет непримитивный тип, то нет оценки короткого замыкания, и перегруженная функция может быть чем угодно - даже не связанной с обычным использованием.

Компилятор выполнит свои оптимизации по-своему. Возможно, это может привести к дефиниции Моргана, но не на уровне замены условия.

Ответ 7

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

Ответ 8

Но в С++ можно перегрузить операторы, и перегруженный оператор может не всегда уважать это свойство.

Перегруженный оператор больше не является оператором, это вызов функции.

class Boolean
{
  bool value;

  ..

  Boolean operator||(const Boolean& b)
  {
      Boolean c;
      c.value = this->value || b.value;
      return c;
  }

  Boolean logical_or(const Boolean& b)
  {
      Boolean c;
      c.value = this->value || b.value;
      return c;
  }
}

Итак, эта строка кода

Boolean a (true);
Boolean b (false);

Boolean c = a || b;

эквивалентно этому

Boolean c = a.logical_or(b);