Оптимизация закона Де Моргана с перегруженными операторами
Каждый программист должен знать, что:
(Законы Моргана)
В некоторых случаях, чтобы оптимизировать программу, может случиться, что компилятор изменяет (!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);